/*********************************************************
 *           Bugblatter Whois Extension - V3.8           *
 *********************************************************
 *                                                       *
 * This plug-in is linux & win32 friendly.               *
 *                                                       *
 * Version history                                       *
 *                                                       *
 * Version 3.7                                           *
 *                                                       *
 *  - BUGFIX: All dates in August were recorded as being *
 *    in June!                                           *
 *                                                       *
 * Version 3.7                                           *
 *                                                       *
 *  - Integration with cheating-death. Whois will report *
 *    K:D ratios with/without c-d running, and can act   *
 *    as a c-d mediator if c-d is in optional mode       *
 *  - bbwhois report shortened, and bbwhois_full added   *
 *    to display the complete report.                    *
 *  - Kicked and Banned messages in players console made *
 *    more prominent.                                    *
 *                                                       *
 *                                                       *
 * Version 3.6                                           *
 *                                                       *
 *  - Reduced repetitions of some announcements          *
 *  - No warnings compiling on Adminmod 2.50.56          *
 *  - Copes with SteamIDs and ValveIDs as well as WONIDs *
 *  - Problems with case sensitive filenames on Linux    *
 *    fixed. No longer re-watches players unwatched and  *
 *    some broken detections fixed.                      *
 *  - Remembers upto 5 old names used in this game for   *
 *    each player so if someone changes their name and   *
 *    you don't notice you can still whois them on their *
 *    old name                                           *
 *  - Supports ban durations 1h,1d,1w,1m,1y etc.         *
 *  - Bug fix - used to report to other admins that a    *
 *    fellow admin had watched someone when they had     *
 *    actually kicked or banned them.                    *
 *                                                       *
 * Version 3.5                                           *
 *                                                       *
 *  - No longer auto-watches a player who connects from  *
 *    the same IP as someone who has been temp banned.   *
 *  - Option to turn off team kill detection for non-    *
 *    team based games.                                  *
 *  - Within the same round reports if a player rejoins  *
 *    after being kicked, temp banned and/or rejoins     *
 *    with a different name to the one the left with.    *
 *    A single alert is sent to all ACCESS_WHOIS level   *
 *    admins.                                            *
 *  - Tells other admins in chat when another admin or   *
 *    hlguard uses note/watch/unwatch/kick/ban on a      *
 *    player that is (or was in the case of kick/ban ;)) *
 *    connected to the server.                           *
 *                                                       *
 * Version 3.4                                           *
 *                                                       *
 *  - Now detects are reports on team kills              *
 *  - Now alerts when two players connect from the same  *
 *    IP address                                         *
 *  - bbwhois_lan command added to recap who is lanning  *
 *  - Added CDATA tags around names to prevent problems  *
 *    with XML display                                   *
 *  - Corrected bug in GetTopName that returned the last *
 *    name in the file as the top name all the time.     *
 *    This should may the admins top name appear in the  *
 *    whois reports and the correct Aka appear in the    *
 *    report title                                       *
 *  - Added first connection date to the report header   *
 *  - Ratios are now displayed as approximated ratios    *
 *    rather than percentages                            *
 *  - IP address and access level line not included in   *
 *    report if the player isn't connected               *
 *  - bbwhois_watch and bbwhois_unwatch wouldn't work    *
 *    when require reason was turned on and you did      *
 *    supply a reason.                                   *
 *  - bbwhois_watch and bbwhois_unwatch now work on      *
 *    players that have left the server and on WONIDs    *
 *    of anyone connected or otherwise                   *
 *  - bbwhois_note, bbwhois_watch and bbwhois_unwatch    *
 *    now return the player name match in the            *
 *    confirmation message to the admin using them       *
 *  - bbwhois now reports a usage message if called with *
 *    no arguments.                                      *
 *  - when any ban/note/watch/unwatch is performed on a  *
 *    a player not connected, their most common name is  *
 *    written to the global notes file instead of        *
 *    "Unknown player not connected"                     *
 *                                                       *
 * Version 3.3                                           *
 *                                                       *
 *  - Access level of ACCESS_WHOIS changed to the unused *
 *    1024 level. This allows you to give access to the  *
 *    bbwhois, bbwhois_note and bbwhois_watch commands   *
 *    to server regulars who are not admins to allow     *
 *    to alert you to cheats                             *
 *                                                       *
 *  - bbwhois_admin command added to report who is admin *
 *                                                       *
 *  - Names the player to watch if there is only one     *
 *    when an admin connects                             *
 *                                                       *
 *  - hlguard integration - whois reports will now show  *
 *    actions / suspicions of hlguard                    *
 *                                                       *
 * Version 3.2                                           *
 *                                                       *
 *  - Works correctly on linux                           *
 *  - Uses the top admin name when recording their       *
 *    actions rather than their name at the time         *
 *  - Support for mods other than CS                     *
 *  - Always creates the WONID.xml file for every player *
 *  - WONID.xml now contains the players AuthID in the   *
 *    player tag                                         *
 *  - Records IP addresses                               *
 *  - Writing data at end of round is now delayed 2      *
 *    seconds so lag isn't during last death so players  *
 *    don't feel they were killed by the lag             *
 *  - Writes all notes to a global log file as well as   *
 *    the player specific notes files so server owner    *
 *    can review all admin actions.                      *
 *  - XSL files are now located in the db folder so that *
 *    "db" can be shared via HTTP to provide web based   *
 *    access to the XML files                            *
 *  - Cross-refrences IP addresses to find other players *
 *    from the same subnet                               *
 *  - Automatically sets a watch on a player who connects*
 *    from the same IP address/subnet as a banned player *
 *    or a watched player unless they have been          *
 *    unwatched previously                               *
 *  - BUGFIX: bbwhois_watch without a reason reported    *
 *    watches instead of adding one                      *
 *    from the same subnet                               *
 *  - Numerous fixes to admin_ban/admin_unban including  *
 *    using ban/banip correctly, telling the user the    *
 *    reason they were banned and giving the admin an    *
 *    error message if they supply a reason in the time  *
 *    field.                                             *
 *  - BUGFIX: cope with players having more than 10      *
 *    notes correctly                                    *
 *                                                       *
 * Version 3.1 alpha version:                            *
 *  - Initial Version                                    *
 *********************************************************
 
 
 
*/
#pragma dynamic 8192
#define ROUND_TIME 600
 
#include <core>
#include <console>
#include <string>
#include <admin>
#include <adminlib>
#include <plugin>
 
#include "settings"
#include "clientio"
#include "ip"
#include "language"
#include "stringx"
#include "time"
 
#define ACCESS_WHOIS 1024
 
#define DEFAULT_REPORT 1
#define MIN_REPORT 0
#define MAX_REPORT 1
 
#define DEFAULT_REQUIREREASON 0
#define MIN_REQUIREREASON 0
#define MAX_REQUIREREASON 1
 
#define DEFAULT_TEAMKILL 1
#define MIN_TEAMKILL 0
#define MAX_TEAMKILL 1
 
#define DEFAULT_RCD_MINIMUM 15
#define MIN_RCD_MINIMUM 5
#define MAX_RCD_MINIMUM 10000
 
 
#define DEFAULT_RCD_MAXIMUM 60
#define MIN_RCD_MAXIMUM 5
#define MAX_RCD_MAXIMUM 10000
 
#define DEFAULT_RCD_RATIO 3
#define MIN_RCD_RATIO 1
#define MAX_RCD_RATIO 10
 
#define DEFAULT_RCD_ENABLED 0
#define MIN_RCD_ENABLED 0
#define MAX_RCD_ENABLED 1
 
#define MAX_OLD_PLAYERS 300
#define MAX_OLD_NAMES_PER_PLAYER 5
 
 
#define KD_NOCD_ADMIN 0
#define KD_NOCD_NOADMIN 1
#define KD_CD_ADMIN 2
#define KD_CD_NOADMIN 3
 
new g_Version[]="3.7";
 
 
new MSG_REPORTTITLE[]="Bugblatter Whois configuration:";
new MSG_RPT_REASONDISABLED[]=" - Providing a reason is optional (bbwhois_reqirereason)";
new MSG_RPT_REASONENABLED[]=" - Providing a reason is required (bbwhois_reqirereason)";
new MSG_RPT_TEAMKILL_ON[]=" - Logging of team kills is enabled (bbwhois_teamkills)";
new MSG_RPT_TEAMKILL_OFF[]=" - Logging of team kills is disabled (bbwhois_teamkills)";
new MSG_CDREPORTTITLE[]="\nBugblatter Whois <-> Cheating Death configuration: (bbwhois_cdrequired)";
new MSG_RPT_RCD_ON[] =      " - CD Mediation enabled";
new MSG_RPT_RCD_OFF[] =     " - CD Mediation disabled";
new MSG_RPT_RCD_MAXIMUM[] = " - Force players to use CD after %1i rounds";
new MSG_RPT_RCD_RATIO[] =   " - Force players with a %1i:1 kills/deaths to use CD";
new MSG_RPT_RCD_MINIMUM[] = " - Ignore kills/deaths ratio for the first %1i rounds";
 
 
 
/* Config */
new g_RequireReason=DEFAULT_REQUIREREASON;
new g_Report=DEFAULT_REPORT;
new g_TeamKill=DEFAULT_TEAMKILL;
 
/* State */
new g_Kills[MAX_PLAYERS][4];                            /* Kills with admin connected */
new g_Deaths[MAX_PLAYERS][4];                           /* Deaths with admin connected */
new g_IPs[MAX_PLAYERS];                                 /* Names of the currently conencted players */
new g_Connections[MAX_PLAYERS];                         /* 1 for those slots that have a player connected but not logged as such */
new g_Rounds[MAX_PLAYERS];                              /* Total rounds played */
new g_Watches[MAX_PLAYERS];                             /* 1 for those slots where the connected player is watched */
new g_Registered=0;                                     /* 1 indicates this plugin has initialised correctly */
new g_AuthIDs[MAX_PLAYERS][MAX_AUTHID_LENGTH];          /* AuthID cache for each connected player */
new g_Kicked[MAX_PLAYERS];                              /* Flags a player has been kicked/banned so g_OldKicked can be set on disconnect */
 
new g_OldNames[MAX_OLD_PLAYERS][MAX_NAME_LENGTH];       /* Names of players that have disconnected in this game */
new g_OldAuthIDs[MAX_OLD_PLAYERS][MAX_AUTHID_LENGTH];   /* AuthIDs associated with each previous name */
new g_OldKicked[MAX_OLD_PLAYERS];                       /* Flags if old players were kicked or banned */
new g_OldPtr=0;                                         /* Indicates number of slots used in g_OldNames */
 
new g_TopNames[MAX_PLAYERS][MAX_NAME_LENGTH];           /* Records the top name of each connected player at the point they connected */
new g_TeamKills[MAX_PLAYERS];                           /* Holds the index of the killer of each player if death was by team kill */
new g_TeamKillsLast[MAX_PLAYERS];                       /* g_TeamKills from the last round */
new g_RoundStartTime=0;                                 /* Seconds since 1/1/1970 00:00:00 when current round started */
new g_LanDetect[MAX_PLAYERS];                           /* Used during processing bbwhois_lan to repeat duplicate reports */
 
new g_CheatingDeath=0;
new g_CDRequiredMode=0;
new g_CD_Status[MAX_PLAYERS];
new g_RcdMinimum = DEFAULT_RCD_MINIMUM;
new g_RcdMaximum = DEFAULT_RCD_MAXIMUM;
new g_RcdRatio = DEFAULT_RCD_RATIO;
new g_RcdEnabled = DEFAULT_RCD_ENABLED;
 
new g_AdminConnected=0;
 
forward RegisterPlugin();
forward BBWhois(HLCommand,HLData,HLUserName,UserIndex);
forward BBWhoisNote(HLCommand,HLData,HLUserName,UserIndex);
forward BBWhoisWatch(HLCommand,HLData,HLUserName,UserIndex);
forward BBWhoisUnWatch(HLCommand,HLData,HLUserName,UserIndex);
forward BBWhoisAdmin(HLCommand,HLData,HLUserName,UserIndex);
forward BBWhoisLan(HLCommand,HLData,HLUserName,UserIndex);
forward BBWhoisReset(HLCommand,HLData,HLUserName,UserIndex);
forward BBWhoisReport(HLCommand,HLData,HLUserName,UserIndex);
forward BBWhoisRR(HLCommand,HLData,HLUserName,UserIndex);
forward BBWhoisTeamKill(HLCommand,HLData,HLUserName,UserIndex);
forward ShowConfig(UserIndex,Force);
forward admin_ban(HLCommand,HLData,HLUserName,UserIndex);
forward admin_kick(HLCommand,HLData,HLUserName,UserIndex);
forward admin_unban(HLCommand,HLData,HLUserName,UserIndex);
forward BBLogdWorldAction(HLCommand,HLData,HLUserName,UserIndex);
forward BBLogdKill(HLCommand,HLData,HLUserName,UserIndex);
forward OnEndOfRound();
forward BBWhoisHLGuard(HLCommand,HLData,HLUserName,UserIndex);
forward LogWriteTimer(Timer,Repeat,HLUserName,HLParam);
forward LogConnectionTimer(Timer,Repeat,HLUserName,HLParam);
forward LogConnection(UserIndex);
forward LookupConnectCount(Path[],strDate[],&nConnects);
forward LookupNameCount(Path[],strName[],&nRounds);
forward LookupIPCount(Path[],strIP[],&nRounds);
forward LookupAddress(Path[],AuthID[]);
forward LogEndOfRound(UserIndex,UserName[]);
forward LogNames(UserName[],AuthID[]);
forward LogIPs(UserIndex,AuthID[]);
forward LogKills(UserIndex,AuthID[]);
forward InitNotesFile(AuthID[]);
forward LogNote(NoteType[],Target[],AdminIndex,AdminName[],Note[],Data[],Direct);
forward WriteSettings(UserIndex);
forward WriteSettingsByAuthID(AuthID[],Watch);
forward ReadSettings(UserIndex);
forward AnnounceWatches(Timer,Repeat,HLUserName,HLParam);
forward AnnounceWatchme(Timer,Repeat,HLUserName,HLParam);
forward ReportWatches(UserIndex);
forward ReportLan(UserIndex);
forward ReportAdmins(UserIndex);
forward ReportHeader(UserIndex,AuthID[],Online,AKA[],fFull);
forward ReportConnections(UserIndex,AuthID[]);
forward ReportNames(Timer,Repeat,HLUserName,HLParam);
forward ReportNamesShort(Timer,Repeat,HLUserName,HLParam);
forward ReportIPs(Timer,Repeat,HLUserName,HLParam);
forward ReportTop10(User[],AuthID[],strFile[],strPrompt[],nCount);
forward ReportOtherIDs(Timer,Repeat,HLUserName,HLParam);
forward ReportNotes(Timer,Repeat,HLUserName,HLParam);
forward GetTopName(AuthID[],strCurName[],strTopName[],&Second);
forward Resolve(Data[],AuthID[],&TargetIndex,TargetName[],AKA[]);
forward IsAdminConnected();
forward Hash(strin[]);
forward GetDate(strDate[]);
forward GetAuthID(UserIndex,AuthID[MAX_AUTHID_LENGTH]);
forward SplitDate(strDate[],&year,&month,&day);
forward DaysDiff(year1,month1,day1,year2,month2,day2);
forward CheckCrossReference(TargetIndex);
forward CheckIPFile(TargetIndex,Path[],AuthID[]);
forward CheckIPSetWatch(TargetIndex,AuthID[],DupAuthID[],Reason[]);
forward HasBeenUnwatched(AuthID[]);
forward CalcRatio(K,D,Ratio[]);
forward CheckIP(UserIndex,AdminIndex);
forward CheckSubnet(UserIndex,AdminIndex);
forward CheckOldNames(NewName[],AuthID[]);
forward GenerateFilename(Path[],ParsedAuthID[],&HashParsedAuthID,Base[],AuthID[],FileType[]);
forward ParseAuthID(AuthID[],ParsedAuthID[]);
forward RemoveCD(Name[]);
 
public plugin_init()
{
 
  plugin_registerinfo("Bugblatter's Whois plugin","Maintains a player information database",g_Version);
 
  /* Check server is configured properly */
  new fOK = checkFileAccessRead();
  fOK = checkFileAccessWrite() && fOK;
  fOK = checkVaultOK() && fOK;
  if (fOK == 0) {
    return PLUGIN_CONTINUE;
  }
  /* No need to abort if this one fails */
  checkLanguage();
  language_init();
 
  readvaultnum("bbwhois_requirereason", g_RequireReason,DEFAULT_REQUIREREASON,MIN_REQUIREREASON,MAX_REQUIREREASON);
  readvaultnum("bbwhois_teamkill", g_TeamKill,DEFAULT_TEAMKILL,MIN_TEAMKILL,MAX_TEAMKILL);
  readvaultnum("bbwhois_rcdminimum", g_RcdMinimum,DEFAULT_RCD_MINIMUM,MIN_RCD_MINIMUM,MAX_RCD_MINIMUM);
  readvaultnum("bbwhois_rcdmaximum", g_RcdMaximum,DEFAULT_RCD_MAXIMUM,MIN_RCD_MAXIMUM,MAX_RCD_MAXIMUM);
  readvaultnum("bbwhois_rcdratio", g_RcdRatio,DEFAULT_RCD_RATIO,MIN_RCD_RATIO,MAX_RCD_RATIO);
  readvaultnum("bbwhois_rcdenabled", g_RcdEnabled,DEFAULT_RCD_ENABLED,MIN_RCD_ENABLED,MAX_RCD_ENABLED);
 
  RegisterPlugin();
 
  set_timer("LogWriteTimer",ROUND_TIME,1,"");
  set_timer("LogConnectionTimer",15,99999,"");
 
  /* Check if cheating death is installed and not disabled */
  new CDVersion[MAX_TEXT_LENGTH];
  if (getstrvar("cdversion",CDVersion,MAX_TEXT_LENGTH)) {
    if (strlen(CDVersion)>0) {
      if (getvar("cdrequired")>=0) {
        g_CheatingDeath = 1;
      }
      if (getvar("cdrequired")==1) {
        g_CDRequiredMode = 1;
      }
    }
  }
 
  new i;
  for (i=0;i<MAX_PLAYERS;i++) {
	g_CD_Status[i]=3;
  }
 
  return PLUGIN_CONTINUE;
}
 
 RegisterPlugin() {
 
  /* Logd callbacks */
  plugin_registercmd("bbwhois_logdwa","BBLogdWorldAction",131072,"");
  exec("logd_reg 62 admin_command bbwhois_logdwa",0);
  plugin_registercmd("bbwhois_logdkill","BBLogdKill",131072,"");
  exec("logd_reg 57 admin_command bbwhois_logdkill",0);
 
  /* Cheating death callback to inform whois if player has validated */
  plugin_registercmd("bbwhois_cdstatus","BBWhoisCDStatus",131072,"");
 
  /* HL Guard callback to report its actions / suspicions */
  plugin_registercmd("bbwhois_hlguard","BBWhoisHLGuard",131072,"");
 
 
  /* Whois Player commands */
  plugin_registercmd("bbwhois","BBWhois",ACCESS_WHOIS,
                     "bbwhois <Player Name | WonID>: Displays a short on player's activities.");
  plugin_registercmd("bbwhois_full","BBWhois",ACCESS_WHOIS,
                     "bbwhois_full <Player Name | WonID>: Displays a full on player's activities.");
 
  plugin_registercmd("bbwhois_note","BBWhoisNote",ACCESS_WHOIS,
                     "bbwhois_note <Player Name | WonID> <Message>: Records a message against the player.");
 
  plugin_registercmd("bbwhois_watch","BBWhoisWatch",ACCESS_WHOIS,
                     "bbwhois_watch <Player Name | WonID> <Reason>: Record player should be watched and logs reason.");
  plugin_registercmd("bbwhois_unwatch","BBWhoisUnWatch",ACCESS_WHOIS,
                     "bbwhois_unwatch <Player Name | WonID> <Reason>: Record player should no longer be watched and logs reason.");
  plugin_registercmd("bbwhois_admin","BBWhoisAdmin",ACCESS_WHOIS,
                     "bbwhois_admin: Lists those on the server with some level of admininstrator access");
  plugin_registercmd("bbwhois_lan","BBWhoisLan",ACCESS_WHOIS,
                     "bbwhois_lan: Lists those using the same IP address");
 
  plugin_registercmd("bbwhois_report","BBWhoisReport",ACCESS_CONFIG,
                     "bbwhois_report [ ^"on^" | ^"off^" ]: shows the whois config, and enables/disable report after every command");
  plugin_registercmd("bbwhois_reset","BBWhoisReset",ACCESS_CONFIG,
                     "bbwhois_reset: sets all whois settings to defaults");
  plugin_registercmd("bbwhois_requirereason","BBWhoisRR",ACCESS_CONFIG,
                     "bbwhois_requirereason ^"on^" | ^"off^": Sets whether reasons must be supplied for watches/gags/kicks/bans etc.");
  plugin_registercmd("bbwhois_teamkill","BBWhoisTeamKill",ACCESS_CONFIG,
                     "bbwhois_teamkill ^"on^" | ^"off^": Enables / disables team kill logging.");
  plugin_registercmd("bbwhois_requirecd","BBWhoisRCD",ACCESS_CONFIG,
                     "bbwhois_requirecd ^"on^" | ^"off^" | n ^"maximum^" | n ^"minimum^" | n ^"ratio^": Controls cheating death.");
 
  /* Reimplementations of standard player commands */
  plugin_registercmd("admin_ban","admin_ban",ACCESS_BAN,"admin_ban <target or WONID or IP> [<minutes>] ['ip']: Bans target. 0 minutes is a permanent ban.");
  plugin_registercmd("admin_banip","admin_ban",ACCESS_BAN,"admin_banip <target or WONID or IP> [<minutes>]: Bans targets ip address. 0 minutes is a permanent ban. ");
  plugin_registercmd("admin_kick","admin_kick",ACCESS_KICK,"admin_kick <target> [<reason>]: Kicks target.");
  plugin_registercmd("admin_unban","admin_unban",ACCESS_BAN,"admin_unban <WONID or IP>: Unbans target.");
 
  g_Registered=1;
}
 
public plugin_connect(HLUserName, HLIP, UserIndex) {
  if (g_Registered ==0) {
    return PLUGIN_FAILURE;
  }
 
 
  new Data[MAX_DATA_LENGTH];
  safe_convert(HLIP,Data,MAX_DATA_LENGTH);
 
  HLIPtoip(HLIP,g_IPs[UserIndex]);
  g_Connections[UserIndex]=1;
  g_AuthIDs[UserIndex][0]=0;
  g_CD_Status[UserIndex]=3;
  g_Rounds[UserIndex]=0;
  return PLUGIN_CONTINUE;
}
 
 
public plugin_disconnect(HLUserName, UserIndex) {
  if (g_Registered ==0) {
    return PLUGIN_FAILURE;
  }
 
  new UserName[MAX_NAME_LENGTH];
  safe_convert(HLUserName, UserName, MAX_NAME_LENGTH);
  LogEndOfRound(UserIndex,UserName);
  RemoveCD(UserName);
 
  g_Connections[UserIndex]=0;
  g_Watches[UserIndex]=0;
  g_TopNames[UserIndex][0]=0;
  g_LanDetect[UserIndex]=0;
 
  if (g_OldPtr<MAX_OLD_PLAYERS) {
    strcpy(g_OldNames[g_OldPtr],UserName,MAX_NAME_LENGTH);
    strcpy(g_OldAuthIDs[g_OldPtr],g_AuthIDs[UserIndex],MAX_AUTHID_LENGTH);
    g_OldPtr++;
 
    new i=g_OldPtr-1;
    while (i>0) {
      if (streq(g_OldAuthIDs[g_OldPtr],g_AuthIDs[UserIndex])) {
      	g_OldKicked[i]=g_Kicked[UserIndex];
      }
      i--;
    }
 
    g_Kicked[UserIndex]=0;
  }
 
  g_AuthIDs[UserIndex][0]=0;
  return PLUGIN_CONTINUE;
}
 
 
public plugin_info(HLOldName, HLNewName, UserIndex) {
 
  if (g_Registered ==0) {
    return PLUGIN_FAILURE;
  }
 
  /* Ignore name changes before we have sussed their AuthID */
  if (g_AuthIDs[UserIndex][0]==0) {
    return PLUGIN_CONTINUE;
  }
 
  new NewName[MAX_NAME_LENGTH];
  new OldName[MAX_NAME_LENGTH];
  safe_convert(HLNewName, NewName, MAX_NAME_LENGTH);
  safe_convert(HLOldName, OldName, MAX_NAME_LENGTH);
  RemoveCD(OldName);
  RemoveCD(NewName);
  /* Ignore calls when the name hasn't changed */
  if (streq(NewName,OldName)) {
    return PLUGIN_CONTINUE;
  }
 
  /* Ignore calls with no old name specified */
  if (strlen(OldName)==0) {
    return PLUGIN_CONTINUE;
  }
 
  /* Count the number of old names we already hold for this player */
  new i=g_OldPtr-1;
  new c=0;
  while (i>0) {
    if (streq(g_AuthIDs[UserIndex],g_OldAuthIDs[i])) {
 
      /* Check the player hasn't changed their name back to one we already hold */
      if (streq(g_OldNames[i],OldName)) {
      	return PLUGIN_CONTINUE;
      }
      c=c+1;
      if (c==MAX_OLD_NAMES_PER_PLAYER) {
        break;
      }
 
    }
    i--;
  }
 
  /* Store old name so it can be resolved */
  if (c==MAX_OLD_NAMES_PER_PLAYER) {
 
    /* Shuffle all the old names for this player along the list so it is the oldest old name that is removed */
    new j=i+1;
    new k=i;
    while (j<g_OldPtr) {
      if (streq(g_AuthIDs[UserIndex],g_OldAuthIDs[i])) {
        strcpy(g_OldNames[k],g_OldNames[j],MAX_NAME_LENGTH);
        k=j;
      }
      j++;
    }
    strcpy(g_OldNames[k],OldName,MAX_NAME_LENGTH);
  }
  else {
    /* Append this old name to the list */
    strcpy(g_OldNames[g_OldPtr],OldName,MAX_NAME_LENGTH);
    strcpy(g_OldAuthIDs[g_OldPtr],g_AuthIDs[UserIndex],MAX_AUTHID_LENGTH);
    g_OldKicked[g_OldPtr]=0;
    g_OldPtr++;
  }
 
  return PLUGIN_CONTINUE;
}
 
 
 
 
 
 
 
 
/* *************** Plugin commands ***************** */
 
public BBWhois(HLCommand,HLData,HLUserName,UserIndex) {
  new Command[MAX_COMMAND_LENGTH];
  new Data[MAX_DATA_LENGTH];
  new TargetIndex;
  new TargetName[MAX_NAME_LENGTH];
  new AuthID[MAX_AUTHID_LENGTH];
  new AKA[MAX_NAME_LENGTH];
  safe_convert(HLCommand,Command,MAX_DATA_LENGTH);
  safe_convert(HLData,Data,MAX_DATA_LENGTH);
  if (strlen(Data)==0) {
    language_say(UserIndex,"Usage: bbwhois <playername or wonid>",print_type:print_console,0,0,0,0);
  }
  else {
    if (Resolve(Data,AuthID,TargetIndex,TargetName,AKA)) {
      ReportHeader(UserIndex,AuthID,TargetIndex,AKA,streq(Command,"bbwhois_full"));
    }
    else {
      language_sayf(UserIndex,"Unknown Player '%1s'",print_type:print_console,0,0,0,0,Data);
    }
  }
  return PLUGIN_HANDLED;
}
 
public BBWhoisNote(HLCommand,HLData,HLUserName,UserIndex) {
  new Data[MAX_DATA_LENGTH];
  new User[MAX_NAME_LENGTH];
  new Target[MAX_DATA_LENGTH];
  new AuthID[MAX_AUTHID_LENGTH];
  new TargetIndex;
  new TargetName[MAX_NAME_LENGTH];
  new AKA[MAX_NAME_LENGTH];
 
  safe_convert(HLData,Data,MAX_DATA_LENGTH);
  safe_convert(HLUserName,User,MAX_NAME_LENGTH);
  RemoveCD(User);
  strbreak(Data,Target,Data,MAX_DATA_LENGTH);
 
  if (Resolve(Target,AuthID,TargetIndex,TargetName,AKA)) {
    if (TargetIndex>0) {
      LogNote("Note",AuthID,UserIndex,User,Data,"",0);
      if (strlen(AKA) >0) {
      	language_sayf(UserIndex,"Note added for player '%1s' now playing as '%2s'.",print_type:print_console,0,0,0,0,AKA,TargetName);
      }
      else {
        language_sayf(UserIndex,"Note added for player '%1s'.",print_type:print_console,0,0,0,0,TargetName);
      }
      new AdminMsg[MAX_TEXT_LENGTH];
      snprintf(AdminMsg,MAX_DATA_LENGTH,"### %s has added a note for %s: %s",User,TargetName,Data);
      AnnounceToAdmins(AdminMsg,print_type:print_chat,UserIndex);
 
    }
    else {
      LogNote("Note",AuthID,UserIndex,User,Data,"",1);
      language_sayf(UserIndex,"Note added for disconnected player '%1s'.",print_type:print_console,0,0,0,0,TargetName);
    }
  }
  else {
    language_sayf(UserIndex,"Unknown Player '%1s'",print_type:print_console,0,0,0,0,Data);
  }
 
  return PLUGIN_HANDLED;
}
 
public BBWhoisWatch(HLCommand,HLData,HLUserName,UserIndex) {
  new Data[MAX_DATA_LENGTH];
  new User[MAX_NAME_LENGTH];
  new Target[MAX_DATA_LENGTH];
  new TargetIndex;
  new TargetName[MAX_NAME_LENGTH];
  new AuthID[MAX_AUTHID_LENGTH];
  new AKA[MAX_NAME_LENGTH];
 
  safe_convert(HLData,Data,MAX_DATA_LENGTH);
  safe_convert(HLUserName,User,MAX_NAME_LENGTH);
  RemoveCD(User);
 
  if (strlen(Data)==0) {
    /* Reporting on watches */
    ReportWatches(UserIndex);
    return PLUGIN_HANDLED;
  }
 
  strbreak(Data,Target,Data,MAX_DATA_LENGTH);
  if ((strlen(Data)==0) && (g_RequireReason==1)) {
    language_say(UserIndex,"You must supply a reason when using bbwhois_watch",print_type:print_console,0,0,0,0);
    return PLUGIN_HANDLED;
  }
 
  /* Activating a watch */
  if (Resolve(Target,AuthID,TargetIndex,TargetName,AKA)) {
    if (TargetIndex>0) {
      g_Watches[TargetIndex]=1;
      WriteSettings(TargetIndex);
 
      if (strlen(AKA) >0) {
        language_sayf(UserIndex,"Watch added for player '%1s' now playing as '%2s'.",print_type:print_console,0,0,0,0,AKA,TargetName);
      }
      else {
        language_sayf(UserIndex,"Watch added for player '%1s'.",print_type:print_console,0,0,0,0,TargetName);
      }
 
      LogNote("Watch",AuthID,UserIndex,User,Data,"",0);
 
      new AdminMsg[MAX_TEXT_LENGTH];
      snprintf(AdminMsg,MAX_DATA_LENGTH,"### %s has watched %s, reason: %s",User,TargetName,Data);
      AnnounceToAdmins(AdminMsg,print_type:print_chat,UserIndex);
 
    }
    else {
      WriteSettingsByAuthID(AuthID,1);
      language_sayf(UserIndex,"Watch added for disconnected player '%1s'.",print_type:print_console,0,0,0,0,TargetName);
      LogNote("Watch",AuthID,UserIndex,User,Data,"",1);
    }
 
  }
  else {
    language_sayf(UserIndex,"Unknown player '%1s'",print_type:print_console,0,0,0,0,Target);
  }
 
  return PLUGIN_HANDLED;
}
 
public BBWhoisUnWatch(HLCommand,HLData,HLUserName,UserIndex) {
  new Data[MAX_DATA_LENGTH];
  new User[MAX_NAME_LENGTH];
  new Target[MAX_DATA_LENGTH];
  new TargetIndex;
  new TargetName[MAX_DATA_LENGTH];
  new AuthID[MAX_AUTHID_LENGTH];
  new AKA[MAX_NAME_LENGTH];
 
  safe_convert(HLData,Data,MAX_DATA_LENGTH);
  safe_convert(HLUserName,User,MAX_NAME_LENGTH);
  RemoveCD(User);
 
  strbreak(Data,Target,Data,MAX_DATA_LENGTH);
  if ((strlen(Data)==0) && (g_RequireReason==1)) {
    language_say(UserIndex,"You must supply a reason when using bbwhois_unwatch",print_type:print_console,0,0,0,0);
    return PLUGIN_HANDLED;
  }
 
  if (Resolve(Target,AuthID,TargetIndex,TargetName,AKA)) {
    if (TargetIndex>0) {
      g_Watches[TargetIndex]=0;
      WriteSettings(TargetIndex);
      LogNote("Unwatch",AuthID,UserIndex,User,Data,"",0);
      if (strlen(AKA) >0) {
        language_sayf(UserIndex,"Watch removed for player '%1s' now playing as '%2s'.",print_type:print_console,0,0,0,0,AKA,TargetName);
      }
      else {
        language_sayf(UserIndex,"Watch removed for player '%1s'.",print_type:print_console,0,0,0,0,TargetName);
      }
 
      new AdminMsg[MAX_TEXT_LENGTH];
      snprintf(AdminMsg,MAX_DATA_LENGTH,"### %s has unwatched %s, reason: %s",User,TargetName,Data);
      AnnounceToAdmins(AdminMsg,print_type:print_chat,UserIndex);
 
    }
    else {
      WriteSettingsByAuthID(AuthID,0);
      language_sayf(UserIndex,"Watch removed for disconnected player '%1s'.",print_type:print_console,0,0,0,0,TargetName);
      LogNote("Unwatch",AuthID,UserIndex,User,Data,"",1);
    }
  }
  else {
    language_sayf(UserIndex,"Unknown player '%1s'",print_type:print_console,0,0,0,0,Target);
  }
 
  return PLUGIN_HANDLED;
}
 
public BBWhoisAdmin(HLCommand,HLData,HLUserName,UserIndex) {
  ReportAdmins(UserIndex);
  return PLUGIN_HANDLED;
}
 
public BBWhoisLan(HLCommand,HLData,HLUserName,UserIndex) {
  ReportLan(UserIndex);
  return PLUGIN_HANDLED;
}
 
 
 
public BBWhoisReset(HLCommand,HLData,HLUserName,UserIndex) {
  g_Report=DEFAULT_REPORT;
  g_RequireReason=DEFAULT_REQUIREREASON;
 
  writevaultnum("bbwhois_requirereason",g_RequireReason);
  writevaultnum("bbwhois_report",g_Report);
  return ShowConfig(UserIndex,0);
}
 
public BBWhoisReport(HLCommand,HLData,HLUserName,UserIndex) {
  if (readHLonoff(HLData,g_Report,DEFAULT_REPORT,MIN_REPORT,MAX_REPORT)) {
    writevaultnum("bbwhois_report",g_Report);
    ShowConfig(UserIndex,0);
  }
  else {
    ShowConfig(UserIndex,1);
  }
  return PLUGIN_HANDLED;
}
 
public BBWhoisRR(HLCommand,HLData,HLUserName,UserIndex) {
  if (readHLonoff(HLData,g_RequireReason,DEFAULT_REQUIREREASON,MIN_REQUIREREASON,MAX_REQUIREREASON)) {
    writevaultnum("bbwhois_requirereason",g_RequireReason);
  }
  return ShowConfig(UserIndex,0);
}
 
public BBWhoisTeamKill(HLCommand,HLData,HLUserName,UserIndex) {
  if (readHLonoff(HLData,g_TeamKill,DEFAULT_TEAMKILL,MIN_TEAMKILL,MAX_TEAMKILL)) {
    writevaultnum("bbwhois_teamkill",g_TeamKill);
  }
  return ShowConfig(UserIndex,0);
}
ShowConfig(UserIndex,Force) {
  if ((Force==0) && (g_Report==0)) {
    return PLUGIN_HANDLED;
  }
 
  language_say(UserIndex,MSG_REPORTTITLE,print_type:print_console,0,0,0,0);
  if (g_RequireReason==0) {
    language_say(UserIndex,MSG_RPT_REASONDISABLED,print_type:print_console,0,0,0,0);
  }
  else {
    language_say(UserIndex,MSG_RPT_REASONENABLED,print_type:print_console,0,0,0,0);
  }
  if (g_TeamKill==0) {
    language_say(UserIndex,MSG_RPT_TEAMKILL_OFF,print_type:print_console,0,0,0,0);
  }
  else {
    language_say(UserIndex,MSG_RPT_TEAMKILL_ON,print_type:print_console,0,0,0,0);
  }
 
  language_say(UserIndex,MSG_CDREPORTTITLE,print_type:print_console,0,0,0,0);
  if (g_RcdEnabled) {
	language_say(UserIndex,MSG_RPT_RCD_ON,print_type:print_console,0,0,0,0);
  }
  else {
	language_say(UserIndex,MSG_RPT_RCD_OFF,print_type:print_console,0,0,0,0);
  }
  language_sayf(UserIndex,MSG_RPT_RCD_MAXIMUM,print_type:print_console,0,0,0,0,g_RcdMaximum);
  language_sayf(UserIndex,MSG_RPT_RCD_RATIO,print_type:print_console,0,0,0,0,g_RcdRatio);
  language_sayf(UserIndex,MSG_RPT_RCD_MINIMUM,print_type:print_console,0,0,0,0,g_RcdMinimum);
 
  return PLUGIN_HANDLED;
}
 
public BBWhoisRCD(HLCommand,HLData,HLUserName,UserIndex) {
  new strData[MAX_DATA_LENGTH];
  new strValue[MAX_DATA_LENGTH];
  new strType[MAX_DATA_LENGTH];
  new nValue=0;
 
  safe_convert(HLData,strData,MAX_DATA_LENGTH);
  new args=strsplit(strData," ",strValue,MAX_DATA_LENGTH,strType,MAX_DATA_LENGTH);
  nValue = strtonum(strValue);
 
  if ((args==2) && (strcasecmp(strType,"minimum")==0)) {
    if ( (nValue < MIN_RCD_MINIMUM) || (nValue > MAX_RCD_MINIMUM)) {
      g_RcdMinimum = DEFAULT_RCD_MINIMUM;
    }
    else {
      g_RcdMinimum = nValue;
    }
    writevaultnum("bbwhois_rcdminimum",g_RcdMinimum);
    ShowConfig(UserIndex,0);
  }
  else if ((args==2) && (strcasecmp(strType,"maximum")==0)) {
    if ( (nValue < MIN_RCD_MAXIMUM) || (nValue > MAX_RCD_MAXIMUM)) {
      g_RcdMaximum = DEFAULT_RCD_MAXIMUM;
    }
    else {
      g_RcdMaximum = nValue;
    }
    writevaultnum("bbwhois_rcdmaximum",g_RcdMaximum);
    ShowConfig(UserIndex,0);
  }
  else if ((args==2) && (strcasecmp(strType,"ratio")==0)) {
      if ( (nValue < MIN_RCD_RATIO) || (nValue > MAX_RCD_RATIO)) {
        g_RcdRatio = DEFAULT_RCD_RATIO;
      }
      else {
        g_RcdRatio = nValue;
      }
      writevaultnum("bbwhois_rcdratio",g_RcdRatio);
      ShowConfig(UserIndex,0);
  }
  else if ((args==1) && (strcasecmp(strValue,"on")==0)) {
      g_RcdEnabled = 1;
      writevaultnum("bbwhois_rcdenabled",g_RcdEnabled);
      ShowConfig(UserIndex,0);
  }
  else if ((args==1) && (strcasecmp(strValue,"off")==0)) {
      g_RcdEnabled = 0;
      writevaultnum("bbwhois_rcdenabled",g_RcdEnabled);
      ShowConfig(UserIndex,0);
  }
  else {
	  /* TODO: Error message */
  }
  return PLUGIN_HANDLED;
}
 
 
 
 
 
 
 
 
 
 
 
 
/* ************ Replacement Commands ************** */
 
/* admin_ban <target or WONID or IP> [<minutes>] [reason]
   admin_banip <target or WONID or IP> [<minutes>] */
public admin_ban(HLCommand,HLData,HLUserName,UserIndex) {
  new ban_user[MAX_DATA_LENGTH];
  new BanTime = 0;
  new iBanType = bBanByID;
  new Command[MAX_COMMAND_LENGTH];
  new Data[MAX_DATA_LENGTH];
  new strTime[MAX_DATA_LENGTH];
  new strReason[MAX_DATA_LENGTH];
  new Text[MAX_TEXT_LENGTH];
  new TargetName[MAX_NAME_LENGTH];
  new TargetIndex;
  new User[MAX_NAME_LENGTH];
  new strFormattedTime[MAX_TEXT_LENGTH];
 
  convert_string(HLCommand,Command,MAX_COMMAND_LENGTH);
  convert_string(HLData,Data,MAX_DATA_LENGTH);
  convert_string(HLUserName,User,MAX_NAME_LENGTH);
 
  strbreak(Data,ban_user,strTime, MAX_DATA_LENGTH);
  strbreak(strTime, strTime, strReason, MAX_DATA_LENGTH);
 
  if (strlen(strReason)==0 && (g_RequireReason==1)) {
    language_say(UserIndex,"You must supply a reason when using admin_ban (use a time of 0 for a permanent ban)",print_type:print_console,0,0,0,0);
    return PLUGIN_HANDLED;
  }
 
  if (strlen(strTime) != 0) {
    BanTime = parseduration(strTime,strFormattedTime);
    if (BanTime == 0 && strTime[0]!='0') {
      language_say(UserIndex,"To supply a reason when using admin_ban supply a time period first (use a time of 0 for a permanent ban)",print_type:print_console,0,0,0,0);
      return PLUGIN_HANDLED;
    }
  }
 
  numtostr(BanTime,strTime);
 
  if((getvar("sv_lan") == 1) || (strcasecmp(Command, "admin_banip")==0)) iBanType = bBanByIP;
 
  if (check_user(ban_user)==1) {
    get_username(ban_user,TargetName,MAX_NAME_LENGTH);
    get_userindex(ban_user,TargetIndex);
    say_command(User,Command,TargetName);
    if(check_immunity(ban_user)==1) {
      snprintf(Text, MAX_TEXT_LENGTH, "Laf. You can't ban '%s', you silly bear.", TargetName);
      messageex(User,Text,print_type:print_chat);
    } else {
      if (iBanType == bBanByID) {
        LogNote("Ban",TargetName,UserIndex,User,strReason,strTime,0);
      }
      if (strlen(strReason)>0) {
        snprintf(Text,MAX_TEXT_LENGTH,"You have been banned from this server for %s^n^nReason for ban: %s",strFormattedTime,strReason);
      }
      else {
        snprintf(Text,MAX_TEXT_LENGTH,"You have been banned from this server for %s",strFormattedTime);
	  }
      BigConsoleMessage(TargetIndex,Text);
      snprintf(Text,MAX_TEXT_LENGTH,"%i %i %s",iBanType,BanTime,ban_user);
      set_timer("DelayedBan",1,1,Text);
      g_Kicked[TargetIndex]=BanTime;
 
      new AdminMsg[MAX_TEXT_LENGTH];
      RemoveCD(User);
      snprintf(AdminMsg,MAX_DATA_LENGTH,"### %s has banned %s, reason: %s",User,ban_user,strReason);
      AnnounceToAdmins(AdminMsg,print_type:print_chat,UserIndex);
 
    }
  } else {
    say_command(User,Command,ban_user);
    if (iBanType == bBanByID) {
      RemoveCD(User);
      LogNote("Ban",ban_user,UserIndex,User,strReason,strTime,1);
    }
    ban(ban_user,BanTime,iBanType);
  }
  return PLUGIN_HANDLED;
}
 
/* Delayed ban is to give time for the big console message to be sent */
 
public DelayedBan(Timer,Repeat,HLUserName,HLParam) {
  new strBanInfo[MAX_TEXT_LENGTH];
  new strType[MAX_TEXT_LENGTH];
  new strTime[MAX_TEXT_LENGTH];
 
  convert_string(HLParam,strBanInfo,MAX_TEXT_LENGTH);
  strbreak(strBanInfo,strType,strBanInfo,MAX_TEXT_LENGTH);
  strbreak(strBanInfo,strTime,strBanInfo,MAX_TEXT_LENGTH);
  ban(strBanInfo,strtonum(strTime),strtonum(strType));
  return PLUGIN_HANDLED;
}
 
 
/* admin_kick <target> [<reason>] */
public admin_kick(HLCommand,HLData,HLUserName,UserIndex) {
  new Command[MAX_COMMAND_LENGTH];
  new Data[MAX_DATA_LENGTH];
  new kick_user[MAX_DATA_LENGTH];
  new Reason[MAX_DATA_LENGTH];
  new Text[MAX_TEXT_LENGTH];
  new User[MAX_NAME_LENGTH];
  new TargetIndex;
 
  convert_string(HLCommand,Command,MAX_COMMAND_LENGTH);
  convert_string(HLData,Data,MAX_DATA_LENGTH);
  convert_string(HLUserName,User,MAX_NAME_LENGTH);
  strbreak(Data,kick_user,Reason,MAX_DATA_LENGTH);
  if (strlen(Reason)==0 && (g_RequireReason==1)) {
    language_say(UserIndex,"You must supply a reason when using admin_kick",print_type:print_console,0,0,0,0);
    return PLUGIN_HANDLED;
  }
  if ( check_user(kick_user) == 1) {
    new real_user[MAX_NAME_LENGTH];
    get_username(kick_user,real_user,MAX_NAME_LENGTH);
    get_userindex(kick_user,TargetIndex);
    say_command(User,Command,real_user);
    if(check_immunity(kick_user)!=0) {
      snprintf(Text, MAX_TEXT_LENGTH, "Laf. You can't kick '%s', you silly bear.", real_user);
      messageex(User, Text, print_type:print_chat);
    } else {
      if (strlen(Reason) != 0) {
        snprintf(Text, MAX_TEXT_LENGTH, "You have been kicked from this server^n^nReason for kick: %s", Reason);
      }
      else {
        strcpy(Text, "You have been kicked from this server", MAX_TEXT_LENGTH);
      }
      BigConsoleMessage(TargetIndex,Text);
 
      RemoveCD(User);
      LogNote("Kick",real_user,UserIndex,User,Reason,"",0);
      set_timer("DelayedKick",2,1,real_user);
      g_Kicked[TargetIndex]=-12345678;
      new AdminMsg[MAX_TEXT_LENGTH];
      snprintf(AdminMsg,MAX_DATA_LENGTH,"### %s has kicked %s, reason: %s",User,real_user,Reason);
      AnnounceToAdmins(AdminMsg,print_type:print_chat,UserIndex);
    }
  } else {
    selfmessage("Unrecognized player: ");
    selfmessage(kick_user);
  }
  return PLUGIN_HANDLED;
}
 
public DelayedKick(Timer,Repeat,HLUserName,HLParam) {
  new strUser[MAX_NAME_LENGTH];
  convert_string(HLParam,strUser,MAX_NAME_LENGTH);
  kick(strUser);
  return PLUGIN_HANDLED;
}
 
 
/* admin_unban <WONID or IP> */
public admin_unban(HLCommand,HLData,HLUserName,UserIndex) {
  new Command[MAX_COMMAND_LENGTH];
  new Data[MAX_DATA_LENGTH];
  new User[MAX_NAME_LENGTH];
 
  convert_string(HLCommand,Command,MAX_COMMAND_LENGTH);
  convert_string(HLData,Data,MAX_DATA_LENGTH);
  convert_string(HLUserName,User,MAX_NAME_LENGTH);
 
  new unban_user[MAX_DATA_LENGTH];
  new Reason[MAX_DATA_LENGTH];
  strbreak(Data,unban_user,Reason,MAX_DATA_LENGTH);
 
  if (strlen(Reason)==0 && (g_RequireReason==1)) {
    language_say(UserIndex,"You must supply a reason when using admin_unban",print_type:print_console,0,0,0,0);
    return PLUGIN_HANDLED;
  }
 
  unban(unban_user);
  log_command(User,Command,Data);
 
  RemoveCD(User);
  LogNote("Unban",unban_user,UserIndex,User,Reason,"",1);
  return PLUGIN_HANDLED;
}
 
 
 
 
 
 
 
 
 
/* *************** Event Capture ***************** */
 
public BBLogdWorldAction(HLCommand,HLData,HLUserName,UserIndex) {
  new Data[MAX_DATA_LENGTH];
  safe_convert(HLData,Data,MAX_DATA_LENGTH);
  if (strmatch(Data,"Round_End",9)==1) {
    /*LogWriteTimer causes lag, so let the death animation of the last
     *player finish first then call it. Also set backup timer for next round
     *in case this isn't a round based mod */
    set_timer("LogWriteTimer",2,1,"");
    set_timer("LogWriteTimer",ROUND_TIME,1,"");
 
    new i;
    for (i=0;i<MAX_PLAYERS;i++) {
      g_TeamKillsLast[i]=g_TeamKills[i];
      g_TeamKills[i]=0;
    }
  }
 
  if (strmatch(Data,"Round_Start",11)==1) {
    g_RoundStartTime=get_unixtime();
  }
  return PLUGIN_HANDLED;
}
 
public BBLogdKill(HLCommand,HLData,HLUserName,UserIndex) {
  new Data[MAX_DATA_LENGTH];
  new strIndex[MAX_DATA_LENGTH];
  new nKillerIndex;
  new nVictimIndex;
  new nKillerType;
  new nVictimType;
 
  safe_convert(HLData,Data,MAX_DATA_LENGTH);
  strbreak(Data,strIndex,Data,MAX_DATA_LENGTH);
  nKillerIndex=strtonum(strIndex);
  strbreak(Data,strIndex,Data,MAX_DATA_LENGTH);
  nVictimIndex=strtonum(strIndex);
 
  nKillerType = 1-g_AdminConnected;
  nVictimType = 1-g_AdminConnected;
  if (g_CheatingDeath) {
    if (g_CD_Status[nKillerIndex]==0) {
      nKillerType+=2;
    }
    if (g_CD_Status[nVictimIndex]==0) {
      nVictimType+=2;
    }
  }
 
/*  snprintf(Data,MAX_DATA_LENGTH,"Killer: %i/%i/%i, Vicitim: %i/%i/%i,CD: %i, Admin: %i",nKillerIndex,nKillerType,g_CD_Status[nKillerIndex],nVictimIndex,nVictimType,g_CD_Status[nVictimIndex],g_CheatingDeath,g_AdminConnected);
  log(Data);*/
 
  if (nKillerIndex>0 && nKillerIndex<MAX_PLAYERS) {
    g_Kills[nKillerIndex][nKillerType]=g_Kills[nKillerIndex][nKillerType]+1;
  }
 
  if (nVictimIndex>0 && nVictimIndex<MAX_PLAYERS) {
    g_Deaths[nVictimIndex][nVictimType]=g_Deaths[nVictimIndex][nVictimType]+1;
  }
 
 
  if (g_TeamKill) {
    new nKillerTeam;
    new nVictimTeam;
    new KillerAuthID[MAX_AUTHID_LENGTH];
    new VictimAuthID[MAX_AUTHID_LENGTH];
    new VictimName[MAX_NAME_LENGTH];
    new KillerName[MAX_NAME_LENGTH];
    new Msg[MAX_DATA_LENGTH];
    new nDummy;
    new i;
    new strKillTime[30];
 
    if (nKillerIndex>0 && nKillerIndex<MAX_PLAYERS && nVictimIndex>0 && nVictimIndex<MAX_PLAYERS) {
      if (playerinfo(nKillerIndex,KillerName,MAX_NAME_LENGTH,nDummy,nDummy,nKillerTeam,nDummy,KillerAuthID)) {
        if (playerinfo(nVictimIndex,VictimName,MAX_NAME_LENGTH,nDummy,nDummy,nVictimTeam,nDummy,VictimAuthID)) {
          if (nKillerTeam == nVictimTeam) {
            snprintf(strKillTime,30,"%i seconds into round",get_unixtime() - g_RoundStartTime);
            RemoveCD(VictimName);
            if (g_TeamKillsLast[nKillerIndex]==nVictimIndex) {
              snprintf(Msg,MAX_DATA_LENGTH,"On %1s(%2s) who team killed him/her last round.",VictimName,VictimAuthID);
              LogNote("Team Kill",KillerAuthID,0,"Automatic",Msg,strKillTime,0);
            }
 
            if (g_TeamKillsLast[nVictimIndex]==nKillerIndex) {
              snprintf(Msg,MAX_DATA_LENGTH,"On %1s(%2s) twice on consecutive rounds.",VictimName,VictimAuthID);
              LogNote("Team Kill",KillerAuthID,0,"Automatic",Msg,strKillTime,0);
            }
 
            for (i=0;i<MAX_PLAYERS;i++) {
              if (g_TeamKills[i]==nKillerIndex) {
                LogNote("Team Kill",KillerAuthID,0,"Automatic","More than once in the same round",strKillTime,0);
              }
            }
 
            g_TeamKills[nVictimIndex]=nKillerIndex;
          }
        }
      }
    }
  }
  return PLUGIN_HANDLED;
}
 
OnEndOfRound() {
  new c = maxplayercount();
  new i;
  new name[MAX_NAME_LENGTH];
 
  for (i=1;i<=c;i++) {
    if (playerinfo(i,name,MAX_NAME_LENGTH)) {
      LogEndOfRound(i,name);
    }
  }
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
/* *************** HL Guard Callback ***************** */
 
 
public BBWhoisHLGuard(HLCommand,HLData,HLUserName,UserIndex) {
  new Data[MAX_DATA_LENGTH];
  safe_convert(HLData,Data,MAX_DATA_LENGTH);
  new User[MAX_NAME_LENGTH];
  safe_convert(HLUserName,User,MAX_DATA_LENGTH);
  if (streq(User,"Admin")==0) {
    snprintf(Data,MAX_DATA_LENGTH,"Attempt by %s to use HLGuard command.",User);
    log(Data);
    return PLUGIN_HANDLED;
  }
 
  new Event[MAX_DATA_LENGTH];
  new EventUser[MAX_DATA_LENGTH];
  new Msg[MAX_DATA_LENGTH];
  new TargetIndex;
  new ui[2];
  new AdminMsg[MAX_DATA_LENGTH];
  strbreak(Data,Event,Data,MAX_DATA_LENGTH);
  strbreak(Data,EventUser,Data,MAX_DATA_LENGTH);
 
 
   /* Debug - can remove this */
  snprintf(Msg,MAX_DATA_LENGTH,"HLGuard event %s for %s: #%s#",Event,EventUser,Data);
  plugin_message(Msg);
 
 
  /* Ensure we have recorded the connections of everyone connecting so
     hlguard doesn't try to watch players that we haven't read the fact
     we are already watching */
  LogConnectionTimer(0,0,0,0);
 
 
  if (get_userindex(EventUser,TargetIndex)) {
    if (streq(Event,"tempban")) {
      snprintf(Msg,MAX_DATA_LENGTH,"Banned for: %s",Data);
      LogNote("Ban",EventUser,0,"HLGuard",Msg,"120",0);
      snprintf(AdminMsg,MAX_DATA_LENGTH,"### HLGuard has banned %s for 120 minutes for %s",EventUser,Data);
      AnnounceToAdmins(AdminMsg,print_type:print_chat,0);
    }
    else if (streq(Event,"name")) {
      if (g_Watches[TargetIndex]==0) {
        if (HasBeenUnwatched(EventUser)==0) {
          g_Watches[TargetIndex]=1;
          WriteSettings(TargetIndex);
          LogNote("Watch",EventUser,0,"HLGuard","Clan or player name on HLGuard cheat list","",0);
          ui[0]=TargetIndex;
          ui[1]=0;
          set_timer("AnnounceWatchme",6,4,ui);
        }
      }
    }
    else if (streq(Event,"ban")) {
      snprintf(Msg,MAX_DATA_LENGTH,"Banned for: %s",Data);
      LogNote("Ban",EventUser,0,"HLGuard",Msg,"",0);
      snprintf(AdminMsg,MAX_DATA_LENGTH,"### HLGuard has banned %s for %s",EventUser,Data);
      AnnounceToAdmins(AdminMsg,print_type:print_chat,0);
    }
    else if (streq(Event,"kick")) {
      snprintf(Msg,MAX_DATA_LENGTH,"Kicked for: %s",Data);
      LogNote("Kick",EventUser,0,"HLGuard",Msg,"",0);
      snprintf(AdminMsg,MAX_DATA_LENGTH,"### HLGuard has kicked %s for %s",EventUser,Data);
      AnnounceToAdmins(AdminMsg,print_type:print_chat,0);
    }
    else if (streq(Event,"suspect")) {
      if (g_Watches[TargetIndex]==0) {
        if (HasBeenUnwatched(EventUser)==0) {
          g_Watches[TargetIndex]=1;
          WriteSettings(TargetIndex);
          snprintf(Msg,MAX_DATA_LENGTH,"Suspected of use cheat: %s",Data);
          LogNote("Watch",EventUser,0,"HLGuard",Msg,"",0);
          ui[0]=TargetIndex;
          ui[1]=0;
          set_timer("AnnounceWatchme",6,4,ui);
        }
      }
    }
    /* Don't think we care about these two
    else if (streq(Event,"write")) {
    }
    else if (streq(Event,"say")) {
    }
    */
  }
 
  return PLUGIN_HANDLED;
}
 
 
 
/* *************** HL Guard Callback ***************** */
 
public BBWhoisCDStatus(HLCommand,HLData,HLUserName,UserIndex) {
  new Data[MAX_DATA_LENGTH];
  safe_convert(HLData,Data,MAX_DATA_LENGTH);
  new User[MAX_NAME_LENGTH];
  safe_convert(HLUserName,User,MAX_DATA_LENGTH);
  if (streq(User,"Admin")==0) {
    snprintf(Data,MAX_DATA_LENGTH,"Attempt by %s to use CD status command.",User);
    log(Data);
    return PLUGIN_HANDLED;
  }
 
  new strSessionID[MAX_DATA_LENGTH];
  strbreak(Data,strSessionID,Data,MAX_TEXT_LENGTH);
  new nSessionID = strtonum(strSessionID);
  new nStatus = strtonum(Data);
 
  if ((nStatus != 255) && (nStatus != 3)) {
	new nUserIndex = UserIndexFromSessionID(nSessionID);
	if (nUserIndex>0) {
      g_CD_Status[nUserIndex]=nStatus;
      new AuthID[MAX_AUTHID_LENGTH];
      if (GetAuthID(UserIndex,AuthID)) {
        MediateCD(nUserIndex,AuthID);
      }
    }
  }
  return PLUGIN_HANDLED;
}
 
 
 
 
 
 
 
 
 
 
/* *************** Data Logging ***************** */
 
public LogWriteTimer(Timer,Repeat,HLUserName,HLParam) {
  OnEndOfRound();
  return PLUGIN_HANDLED;
}
 
public LogConnectionTimer(Timer,Repeat,HLUserName,HLParam) {
  new c = maxplayercount();
  new i;
  new Name[MAX_NAME_LENGTH];
  new ui[2];
  new wonid;
  new team;
  new dead;
  new sessionid;
  new AuthID[MAX_AUTHID_LENGTH];
 
  for (i=1;i<=c;i++) {
 
 
    if (g_Connections[i]==1) {
      if (playerinfo(i,Name,MAX_NAME_LENGTH,sessionid,wonid,team,dead,AuthID)) {
        if (LogConnection(i)) {
          ReadSettings(i);
          g_Rounds[i]=LookupRoundCount(AuthID);
          if (g_Watches[i]==0) {
            CheckCrossReference(i);
          }
          g_Connections[i]=0;
          if (access(ACCESS_WHOIS,Name)) {
            ui[0]=i;
            ui[1]=0;
            set_timer("AnnounceWatches",5,2,ui);
            ReportLan(i);
          }
          if (g_Watches[i]) {
            ui[0]=i;
            ui[1]=0;
            set_timer("AnnounceWatchme",6,2,ui);
          }
 
          RemoveCD(Name);
          CheckOldNames(Name,AuthID);
 
          CheckIP(i,0);
          if (g_LanDetect[i]==0) {
            CheckSubnet(i,0);
          }
        }
      }
    }
 
 
    if (g_CheatingDeath) {
      if (g_CD_Status[i]==3) {
        if (playerinfo(i,Name,MAX_NAME_LENGTH,sessionid)) {
          new CDCmd[MAX_DATA_LENGTH];
          snprintf(CDCmd,MAX_DATA_LENGTH,"cdstatus ^"admin_command BBWhois_CDStatus^" %i",sessionid);
          exec(CDCmd,0);
        }
      }
    }
 
 
  }
 
  g_AdminConnected=IsAdminConnected();
  return PLUGIN_HANDLED;
}
 
 
 
LogConnection(UserIndex) {
  new AuthID[MAX_AUTHID_LENGTH];
  new Buf[MAX_DATA_LENGTH];
 
  if (GetAuthID(UserIndex,AuthID)==0) {
    return 0;
  }
 
  new Path[MAX_DATA_LENGTH];
  new ParsedAuthID[MAX_AUTHID_LENGTH];
  new HashParsedAuthID;
  GenerateFilename(Path,ParsedAuthID,HashParsedAuthID,"addons/whois/db",AuthID,"Connections.xml");
 
  new nLine=-1;
  new nConnects=0;
  new strDate[MAX_DATE_LENGTH];
  GetDate(strDate);
 
  new nMode=0;
  if (fileexists(Path)) {
    nMode=1;
    nLine=LookupConnectCount(Path,strDate,nConnects);
    if (nLine>0) {
      nMode=2;
    }
  }
 
  if (nMode==0) {
    writefile(Path,"<?xml version=^"1.0^"?>");
    writefile(Path,"<?xml-stylesheet type=^"text/xsl^" href=^"../redirect.xsl^"?>");
    snprintf(Buf,MAX_DATA_LENGTH,"<connections hash=^"%i^" authid=^"%s^">",HashParsedAuthID,ParsedAuthID);
    writefile(Path,Buf,-1);
    writefile(Path,"</connections>");
    nMode=1;
  }
 
  if (nMode==1) {
    writefile(Path,"<connection>",filesize(Path));
    writefile(Path,"<date>");
    writefile(Path,strDate);
    writefile(Path,"</date>");
    writefile(Path,"<count>");
    numtostr(nConnects+1,Buf);
    writefile(Path,Buf);
    writefile(Path,"</count>");
    writefile(Path,"</connection>");
    writefile(Path,"</connections>");
  }
  else {
    numtostr(nConnects+1,Buf);
    writefile(Path,Buf,nLine);
  }
 
  /* Now log to IP cross-referencing database
   * This works on actual AuthIDs rather than parsed AuthID's
   */
  new IP[30];
  iptostr(g_IPs[UserIndex],IP);
  GenerateFilename(Path,ParsedAuthID,HashParsedAuthID,"addons/whois/ips",IP,"address.txt");
  if (LookupAddress(Path,AuthID)==0) {
    writefile(Path,AuthID);
  }
 
  /* And log subnet to cross-referencing database */
  iptostr(g_IPs[UserIndex] & 4294967040,IP); /*4294967040 = 0xFFFFFF00 - masks out last octet */
  GenerateFilename(Path,ParsedAuthID,HashParsedAuthID,"addons/whois/ips",IP,"subnet.txt");
 
  if (LookupAddress(Path,AuthID)==0) {
    writefile(Path,AuthID);
  }
 
  return 1;
}
 
LookupConnectCount(Path[],strDate[],&nConnects) {
  new fs = filesize(Path);
  new i=4;
  new strFDate[MAX_DATE_LENGTH];
  new strFConnects[MAX_NUMBER_LENGTH];
  while(i<fs) {
    readfile(Path,strFDate,i+2,MAX_DATE_LENGTH);
    readfile(Path,strFConnects,i+5,MAX_NUMBER_LENGTH);
    if (streq(strDate,strFDate)) {
      nConnects=strtonum(strFConnects);
      return i+5;
    }
    i=i+8;
  }
  nConnects=0;
  return -1;
}
 
LookupNameCount(Path[],strName[],&nRounds) {
  new fs = filesize(Path);
  new i=4;
  new strFName[MAX_NAME_LENGTH];
  new strFRounds[MAX_NUMBER_LENGTH];
  while (i<fs) {
    readfile(Path,strFName,i+2,MAX_NAME_LENGTH);
    readfile(Path,strFRounds,i+5,MAX_NUMBER_LENGTH);
    if (streq(strName,strFName)) {
      nRounds=strtonum(strFRounds);
      return i+5;
    }
    i=i+8;
  }
  nRounds=0;
  return -1;
}
 
LookupIPCount(Path[],strIP[],&nRounds) {
  new fs = filesize(Path);
  new i=4;
  new strFIP[MAX_NAME_LENGTH];
  new strFRounds[MAX_NUMBER_LENGTH];
  while (i<fs) {
    readfile(Path,strFIP,i+2,MAX_NAME_LENGTH);
    readfile(Path,strFRounds,i+5,MAX_NUMBER_LENGTH);
    if (streq(strIP,strFIP)) {
      nRounds=strtonum(strFRounds);
      return i+5;
    }
    i=i+8;
  }
  nRounds=0;
  return -1;
}
 
LookupAddress(Path[],AuthID[]) {
  new fs = filesize(Path);
  new strFAuthID[MAX_AUTHID_LENGTH];
  new i=1;
  while (i<=fs) {
    readfile(Path,strFAuthID,i,MAX_AUTHID_LENGTH);
    if (streq(strFAuthID,AuthID)) {
      return 1;
    }
    i++;
  }
  return 0;
}
 
LookupRoundCount(AuthID[]) {
  new Path[MAX_DATA_LENGTH];
  new ParsedAuthID[MAX_AUTHID_LENGTH];
  new HashParsedAuthID;
  new nCount=0;
  GenerateFilename(Path,ParsedAuthID,HashParsedAuthID,"addons/whois/db",AuthID,"Names.xml");
  new fs = filesize(Path);
  new i=4;
  new strFRounds[MAX_NUMBER_LENGTH];
  while (i<fs) {
    readfile(Path,strFRounds,i+5,MAX_NUMBER_LENGTH);
    nCount+=strtonum(strFRounds);
    i=i+8;
  }
  return nCount;
}
 
 
LogEndOfRound(UserIndex,UserName[]) {
  new AuthID[MAX_AUTHID_LENGTH];
 
  if (GetAuthID(UserIndex,AuthID)==0) {
    /* Don't log failure here - it will occur for all empty slots! */
    return;
  }
 
  g_Rounds[UserIndex]=g_Rounds[UserIndex]+1;
  LogKills(UserIndex,AuthID);
  RemoveCD(UserName);
  LogNames(UserName,AuthID);
  LogIPs(UserIndex,AuthID);
  MediateCD(UserIndex,AuthID);
}
 
LogNames(UserName[],AuthID[]) {
  new Buf[MAX_DATA_LENGTH];
  new Path[MAX_DATA_LENGTH];
  new ParsedAuthID[MAX_AUTHID_LENGTH];
  new HashParsedAuthID;
  GenerateFilename(Path,ParsedAuthID,HashParsedAuthID,"addons/whois/db",AuthID,"Names.xml");
 
  new nRounds=0;
  new nLine=-1;
 
  new nMode=0;
  if (fileexists(Path)) {
    nMode=1;
    nLine=LookupNameCount(Path,UserName,nRounds);
    if (nLine>0) {
      nMode=2;
    }
  }
 
  if (nMode==0) {
    writefile(Path,"<?xml version=^"1.0^"?>");
    writefile(Path,"<?xml-stylesheet type=^"text/xsl^" href=^"../redirect.xsl^"?>");
    snprintf(Buf,MAX_DATA_LENGTH,"<names hash=^"%i^" authid=^"%s^">",HashParsedAuthID, ParsedAuthID);
    writefile(Path,Buf,-1);
    writefile(Path,"</names>");
    nMode=1;
  }
 
 
  if (nMode==1) {
    writefile(Path,"<name>",filesize(Path));
    writefile(Path,"<alias><![CDATA[");
    writefile(Path,UserName);
    writefile(Path,"]]></alias>");
    writefile(Path,"<count>");
    numtostr(nRounds+1,Buf);
    writefile(Path,Buf);
    writefile(Path,"</count>");
    writefile(Path,"</name>");
    writefile(Path,"</names>");
  }
  else {
    numtostr(nRounds+1,Buf);
    writefile(Path,Buf,nLine);
  }
}
 
LogIPs(UserIndex,AuthID[]) {
  new Buf[MAX_DATA_LENGTH];
  new IP[30];
 
  iptostr(g_IPs[UserIndex],IP);
  new Path[MAX_DATA_LENGTH];
  new ParsedAuthID[MAX_DATA_LENGTH];
  new HashParsedAuthID;
  GenerateFilename(Path,ParsedAuthID,HashParsedAuthID,"addons/whois/db",AuthID,"IPs.xml");
 
  new nRounds=0;
  new nLine=-1;
  new nMode=0;
 
  if (fileexists(Path)) {
    nMode=1;
    nLine=LookupIPCount(Path,IP,nRounds);
    if (nLine>0) {
      nMode=2;
    }
  }
 
  if (nMode==0) {
    writefile(Path,"<?xml version=^"1.0^"?>");
    writefile(Path,"<?xml-stylesheet type=^"text/xsl^" href=^"../redirect.xsl^"?>");
    snprintf(Buf,MAX_DATA_LENGTH,"<ips hash=^"%i^" authid=^"%s^">",HashParsedAuthID, ParsedAuthID);
    writefile(Path,Buf,-1);
    writefile(Path,"</ips>");
    nMode=1;
  }
 
 
  if (nMode==1) {
    writefile(Path,"<ip>",filesize(Path));
    writefile(Path,"<address>");
    writefile(Path,IP);
    writefile(Path,"</address>");
    writefile(Path,"<count>");
    numtostr(nRounds+1,Buf);
    writefile(Path,Buf);
    writefile(Path,"</count>");
    writefile(Path,"</ip>");
    writefile(Path,"</ips>");
  }
  else {
    numtostr(nRounds+1,Buf);
    writefile(Path,Buf,nLine);
  }
}
 
 
LoadKillStatus(Path[],K[],D[]) {
  new Buf[MAX_TEXT_LENGTH];
  new fOK=0;
  if (fileexists(Path)) {
	if (readfile(Path,Buf,6,MAX_DATA_LENGTH)) {
	  K[KD_NOCD_ADMIN]=strtonum(Buf);
	  if (readfile(Path,Buf,9,MAX_DATA_LENGTH)) {
		K[KD_NOCD_NOADMIN]=strtonum(Buf);
		if (readfile(Path,Buf,14,MAX_DATA_LENGTH)) {
		  D[KD_NOCD_ADMIN]=strtonum(Buf);
		  if (readfile(Path,Buf,17,MAX_DATA_LENGTH)) {
			D[KD_NOCD_NOADMIN]=strtonum(Buf);
			fOK=1;
			if (readfile(Path,Buf,22,MAX_DATA_LENGTH)) {
			  K[KD_CD_ADMIN]=strtonum(Buf);
			  if (readfile(Path,Buf,25,MAX_DATA_LENGTH)) {
				K[KD_CD_NOADMIN]=strtonum(Buf);
				if (readfile(Path,Buf,30,MAX_DATA_LENGTH)) {
				  D[KD_CD_ADMIN]=strtonum(Buf);
				  if (readfile(Path,Buf,33,MAX_DATA_LENGTH)) {
					D[KD_CD_NOADMIN]=strtonum(Buf);
				  }
				}
			  }
			}
		  }
		}
	  }
	}
  }
 
  if (fOK==0) {
    K[KD_NOCD_ADMIN]=0;
    K[KD_NOCD_NOADMIN]=0;
    K[KD_CD_ADMIN]=0;
    K[KD_CD_NOADMIN]=0;
    D[KD_NOCD_ADMIN]=0;
    D[KD_NOCD_NOADMIN]=0;
    D[KD_CD_ADMIN]=0;
    D[KD_CD_NOADMIN]=0;
  }
  return fOK;
}
LogKills(UserIndex,AuthID[]) {
  new K[4];
  new D[4];
  new Buf[MAX_DATA_LENGTH];
 
  new Path[MAX_DATA_LENGTH];
  new ParsedAuthID[MAX_AUTHID_LENGTH];
  new HashParsedAuthID;
  GenerateFilename(Path,ParsedAuthID,HashParsedAuthID,"addons/whois/db",AuthID,"Kills.xml");
  LoadKillStatus(Path,K,D);
  /* If file is corrupt, ignore it all */
 
  K[KD_NOCD_ADMIN]+=g_Kills[UserIndex][KD_NOCD_ADMIN];
  K[KD_NOCD_NOADMIN]+=g_Kills[UserIndex][KD_NOCD_NOADMIN];
  K[KD_CD_ADMIN]+=g_Kills[UserIndex][KD_CD_ADMIN];
  K[KD_CD_NOADMIN]+=g_Kills[UserIndex][KD_CD_NOADMIN];
  D[KD_NOCD_ADMIN]+=g_Deaths[UserIndex][KD_NOCD_ADMIN];
  D[KD_NOCD_NOADMIN]+=g_Deaths[UserIndex][KD_NOCD_NOADMIN];
  D[KD_CD_ADMIN]+=g_Deaths[UserIndex][KD_CD_ADMIN];
  D[KD_CD_NOADMIN]+=g_Deaths[UserIndex][KD_CD_NOADMIN];
 
  resetfile(Path);
  resetfile(Path);
  writefile(Path,"<?xml version=^"1.0^"?>");
  writefile(Path,"<?xml-stylesheet type=^"text/xsl^" href=^"../redirect.xsl^"?>");
  snprintf(Buf,MAX_DATA_LENGTH,"<performance hash=^"%i^" authid=^"%s^">",HashParsedAuthID, ParsedAuthID);
  writefile(Path,Buf,-1);
 
  writefile(Path,"<kills>");
  writefile(Path,"<administered>");
  numtostr(K[KD_NOCD_ADMIN],Buf);
  writefile(Path,Buf,-1);
  writefile(Path,"</administered>");
  writefile(Path,"<unmonitored>");
  numtostr(K[KD_NOCD_NOADMIN],Buf);
  writefile(Path,Buf,-1);
  writefile(Path,"</unmonitored>");
  writefile(Path,"</kills>");
 
  writefile(Path,"<deaths>");
  writefile(Path,"<administered>");
  numtostr(D[KD_NOCD_ADMIN],Buf);
  writefile(Path,Buf,-1);
  writefile(Path,"</administered>");
  writefile(Path,"<unmonitored>");
  numtostr(D[KD_NOCD_NOADMIN],Buf);
  writefile(Path,Buf,-1);
  writefile(Path,"</unmonitored>");
  writefile(Path,"</deaths>");
 
  writefile(Path,"<cdkills>");
  writefile(Path,"<administered>");
  numtostr(K[KD_CD_ADMIN],Buf);
  writefile(Path,Buf,-1);
  writefile(Path,"</administered>");
  writefile(Path,"<unmonitored>");
  numtostr(K[KD_CD_NOADMIN],Buf);
  writefile(Path,Buf,-1);
  writefile(Path,"</unmonitored>");
  writefile(Path,"</cdkills>");
 
  writefile(Path,"<cddeaths>");
  writefile(Path,"<administered>");
  numtostr(D[KD_CD_ADMIN],Buf);
  writefile(Path,Buf,-1);
  writefile(Path,"</administered>");
  writefile(Path,"<unmonitored>");
  numtostr(D[KD_CD_NOADMIN],Buf);
  writefile(Path,Buf,-1);
  writefile(Path,"</unmonitored>");
  writefile(Path,"</cddeaths>");
 
  writefile(Path,"</performance>");
 
 
  g_Kills[UserIndex][KD_NOCD_ADMIN]=0;
  g_Kills[UserIndex][KD_NOCD_NOADMIN]=0;
  g_Kills[UserIndex][KD_CD_ADMIN]=0;
  g_Kills[UserIndex][KD_CD_NOADMIN]=0;
  g_Deaths[UserIndex][KD_NOCD_ADMIN]=0;
  g_Deaths[UserIndex][KD_NOCD_NOADMIN]=0;
  g_Deaths[UserIndex][KD_CD_ADMIN]=0;
  g_Deaths[UserIndex][KD_CD_NOADMIN]=0;}
 
InitNotesFile(AuthID[]) {
 
  new Msg[MAX_DATA_LENGTH];
  new Path[MAX_DATA_LENGTH];
  new ParsedAuthID[MAX_DATA_LENGTH];
  new HashParsedAuthID;
  GenerateFilename(Path,ParsedAuthID,HashParsedAuthID,"addons/whois/db",AuthID,"Notes.xml");
 
  if (fileexists(Path)==0) {
    writefile(Path,"<?xml version=^"1.0^"?>");
    writefile(Path,"<?xml-stylesheet type=^"text/xsl^" href=^"../redirect.xsl^"?>");
    snprintf(Msg,MAX_DATA_LENGTH,"<notes hash=^"%i^" authid=^"%s^">",HashParsedAuthID, ParsedAuthID);
    writefile(Path,Msg,-1);
    writefile(Path,"</notes>");
  }
}
 
 
LogNote(NoteType[],Target[],AdminIndex,AdminName[],Note[],Data[],Direct) {
 
  new TargetIndex;
  new TargetName[MAX_DATA_LENGTH];
  new AuthID[MAX_AUTHID_LENGTH];
 
  if (Direct==0) {
    /* Target is playing - get their info from the game */
    if (get_userindex(Target,TargetIndex)==0) {
      selfmessage("Unrecognised player.");
      return;
    }
 
 
    if (GetAuthID(TargetIndex,AuthID)==0) {
      /* log error */
      log("Error getting target authid from target index");
      return;
    }
  }
  else {
    /* Target isn't playing but Target variable contains their WONID
     * lookup their top name */
    new dummy=0;
    GetTopName(Target,"",TargetName,dummy);
    if (TargetName[0]==0) {
      strcpy(TargetName,"[Unknown - Player Not connected]",MAX_DATA_LENGTH);
    }
  }
 
  new AdminAuth[MAX_AUTHID_LENGTH];
  if (AdminIndex!=0) {
    if (GetAuthID(AdminIndex,AdminAuth)==0) {
      /* log error - todo handle console */
      log("Error getting admin authid from admin index");
      return;
    }
  }
 
  new Msg[MAX_DATA_LENGTH];
  new Date[MAX_DATE_LENGTH];
  get_timestamp(Date);
 
 
  /* Write to player-specific notes file */
  new Path[MAX_DATA_LENGTH];
  new ParsedAuthID[MAX_DATA_LENGTH];
  new HashParsedAuthID;
  if (Direct) {
    GenerateFilename(Path,ParsedAuthID,HashParsedAuthID,"addons/whois/db",Target,"Notes.xml");
  }
  else {
    GenerateFilename(Path,ParsedAuthID,HashParsedAuthID,"addons/whois/db",AuthID,"Notes.xml");
  }
 
  if (fileexists(Path)) {
    new fs = filesize(Path);
    writefile(Path,"<note>",fs);
  }
  else {
    writefile(Path,"<?xml version=^"1.0^"?>");
    writefile(Path,"<?xml-stylesheet type=^"text/xsl^" href=^"../redirect.xsl^"?>");
    snprintf(Msg,MAX_DATA_LENGTH,"<notes hash=^"%i^" authid=^"%s^">",HashParsedAuthID, ParsedAuthID);
    writefile(Path,Msg,-1);
    writefile(Path,"<note>");
  }
 
  writefile(Path,"<date>");
  writefile(Path,Date);
  writefile(Path,"</date>");
  writefile(Path,"<type>");
  writefile(Path,NoteType);
  writefile(Path,"</type>");
  if (AdminIndex==0) {
    writefile(Path,"<admin authid=^"^" hash=^"^" ><![CDATA[");
    writefile(Path,AdminName);
  }
  else {
    /*TODO: Parse AdminAuth */
    snprintf(Msg,MAX_DATA_LENGTH,"<admin authid=^"%s^" hash=^"%i^"><![CDATA[", AdminAuth,Hash(AdminAuth));
    writefile(Path,Msg,-1);
    /* record the admin's top name if we have loaded otherwise their current name */
    if (g_TopNames[AdminIndex][0]==0) {
      writefile(Path,AdminName);
    }
    else {
      writefile(Path,g_TopNames[AdminIndex]);
    }
  }
  writefile(Path,"]]></admin>");
  writefile(Path,"<message><![CDATA[");
  writefile(Path,Note);
  writefile(Path,"]]></message>");
  writefile(Path,"<data>");
  writefile(Path,Data);
  writefile(Path,"</data>");
  writefile(Path,"</note>");
  writefile(Path,"</notes>");
 
 
  /* write same data to global notes file */
  strcpy(Path,"addons/whois/db/GlobalNotes.xml",MAX_DATA_LENGTH);
 
  if (fileexists(Path)) {
    new fs = filesize(Path);
    writefile(Path,"<note>",fs);
  }
  else {
    writefile(Path,"<?xml version=^"1.0^"?>");
    writefile(Path,"<?xml-stylesheet type=^"text/xsl^" href=^"player.xsl^"?>");
    writefile(Path,"<notes>");
    writefile(Path,"<note>");
  }
 
 
 
  snprintf(Msg,MAX_DATA_LENGTH,"<target authid=^"%s^" hash=^"%i^"><![CDATA[", ParsedAuthID,HashParsedAuthID);
  writefile(Path,Msg,-1);
  if (Direct) {
    RemoveCD(TargetName);
    writefile(Path,TargetName);
  }
  else {
    writefile(Path,g_TopNames[TargetIndex]);
  }
  writefile(Path,"]]></target>");
 
  writefile(Path,"<date>");
  writefile(Path,Date);
  writefile(Path,"</date>");
  writefile(Path,"<type>");
  writefile(Path,NoteType);
  writefile(Path,"</type>");
 
  if (AdminIndex==0) {
    writefile(Path,"<admin authid=^"^" hash=^"^"><![CDATA[");
    writefile(Path,AdminName);
  }
  else {
    snprintf(Msg,MAX_DATA_LENGTH,"<admin authid=^"%s^" hash=^"%i^"><![CDATA[", AdminAuth,Hash(AdminAuth));
    writefile(Path,Msg,-1);
    /* record the admin's top name if we have loaded otherwise their current name */
    if (g_TopNames[AdminIndex][0]==0) {
      writefile(Path,AdminName);
    }
    else {
      writefile(Path,g_TopNames[AdminIndex]);
    }
  }
  writefile(Path,"]]></admin>");
  writefile(Path,"<message><![CDATA[");
  writefile(Path,Note);
  writefile(Path,"]]></message>");
  writefile(Path,"<data>");
  writefile(Path,Data);
  writefile(Path,"</data>");
  writefile(Path,"</note>");
  writefile(Path,"</notes>");
 
 
}
 
WriteSettings(UserIndex) {
  new AuthID[MAX_AUTHID_LENGTH];
  if (GetAuthID(UserIndex,AuthID)==0) {
    /* log error */
    log("Error getting target authid from target index");
    return;
  }
 
  /* Write to player-specific notes file */
  new Path[MAX_DATA_LENGTH];
  new ParsedAuthID[MAX_DATA_LENGTH];
  new HashParsedAuthID;
  GenerateFilename(Path,ParsedAuthID,HashParsedAuthID,"addons/whois/db",AuthID,".xml");
 
  new Msg[MAX_DATA_LENGTH];
  resetfile(Path);
  writefile(Path,"<?xml version=^"1.0^"?>");
  writefile(Path,"<?xml-stylesheet type=^"text/xsl^" href=^"../player.xsl^"?>");
  snprintf(Msg,MAX_DATA_LENGTH,"<player hash=^"%i^" authid=^"%s^">",HashParsedAuthID,ParsedAuthID);
  writefile(Path,Msg,-1);
  writefile(Path,"<watch>");
  snprintf(Msg,MAX_DATA_LENGTH,"%i",g_Watches[UserIndex]);
  writefile(Path,Msg);
  writefile(Path,"</watch>");
  writefile(Path,"</player>");
}
 
WriteSettingsByAuthID(AuthID[],Watch) {
  new Msg[MAX_DATA_LENGTH];
  new Path[MAX_DATA_LENGTH];
  new ParsedAuthID[MAX_DATA_LENGTH];
  new HashParsedAuthID;
  GenerateFilename(Path,ParsedAuthID,HashParsedAuthID,"addons/whois/db",AuthID,".xml");
 
  if (fileexists(Path)) {
    snprintf(Msg,MAX_DATA_LENGTH,"%i",Watch);
    writefile(Path,Msg,5);
    return 1;
  }
  return 0;
}
 
 
ReadSettings(UserIndex) {
  new AuthID[MAX_AUTHID_LENGTH];
  if (GetAuthID(UserIndex,AuthID)==0) {
    /* log error */
    log("Error getting target authid from target index");
    return;
  }
  new Buf[MAX_DATA_LENGTH];
 
  /* Write to player-specific notes file */
  new Path[MAX_DATA_LENGTH];
  new ParsedAuthID[MAX_DATA_LENGTH];
  new HashParsedAuthID;
  GenerateFilename(Path,ParsedAuthID,HashParsedAuthID,"addons/whois/db",AuthID,".xml");
 
  if (fileexists(Path)) {
    readfile(Path,Buf,5,MAX_DATA_LENGTH);
    g_Watches[UserIndex]=strtonum(Buf);
  }
  else {
    /* First ever connection - init xml files */
    g_Watches[UserIndex]=0;
    InitNotesFile(AuthID);
    WriteSettings(UserIndex);
  }
 
  new TopName[MAX_NAME_LENGTH];
  new Second;
  TopName[0]=0;
  GetTopName(AuthID,"",TopName,Second);
  if (TopName[0]==0) {
    g_TopNames[UserIndex][0]=0;
  }
  else {
    strcpy(g_TopNames[UserIndex],TopName,MAX_NAME_LENGTH);
  }
}
 
 
 
 
 
 
 
 
 
 
 
 
/* *************** Reporting ***************** */
 
/* This is called 3 times at 10 seconds intervals when an admin
 * connects to alert them that there are players to watch on the server */
public AnnounceWatches(Timer,Repeat,HLUserName,HLParam) {
  new c = maxplayercount();
  new i;
  new w=0;
  new Name[MAX_NAME_LENGTH];
  new ui[MAX_DATA_LENGTH];
  new p;
  convert_string(HLParam,ui,MAX_DATA_LENGTH);
 
  for(i=1;i<c;i++) {
    w=w+g_Watches[i];
    if (g_Watches[i]) {
      p=i;
    }
  }
 
  if (w>0) {
    if (w==1) {
      if (playerinfo(p,Name,MAX_NAME_LENGTH)) {
        language_sayf(ui[0],"%1s should be watched - type bbwhois %1s for details.",print_type:print_console,0,0,0,0,Name);
        language_sayf(ui[0],"%1s should be watched - type bbwhois %1s for details.",print_type:print_chat,0,0,0,0,Name);
        return;
      }
    }
 
    language_sayf(ui[0],"%1i players on the server should be watched. Type bbwhois_watch for a list.",print_type:print_console,0,0,0,0,w);
    language_sayf(ui[0],"%1i players on the server should be watched. Type bbwhois_watch for a list.",print_type:print_chat,0,0,0,0,w);
  }
}
 
public AnnounceWatchme(Timer,Repeat,HLUserName,HLParam) {
  new c = maxplayercount();
  new i;
  new ui[MAX_DATA_LENGTH];
  new Name[MAX_NAME_LENGTH];
  new WatchName[MAX_NAME_LENGTH];
 
  convert_string(HLParam,ui,MAX_DATA_LENGTH);
 
  if (playerinfo(ui[0],WatchName,MAX_NAME_LENGTH)) {
    for(i=1;i<c;i++) {
      if (playerinfo(i,Name,MAX_NAME_LENGTH)) {
        if (access(ACCESS_WHOIS,Name)) {
          language_saybynamef(Name,"%1s should be watched - type bbwhois %1s for details.",print_type:print_console,0,0,0,0,WatchName);
          language_saybynamef(Name,"%1s should be watched - type bbwhois %1s for details.",print_type:print_chat,0,0,0,0,WatchName);
        }
      }
    }
  }
}
 
 
ReportWatches(UserIndex) {
  new c=maxplayercount();
  new i;
  new fFirst=1;
  new Name[MAX_NAME_LENGTH];
 
  for(i=1;i<=c;i++) {
    if (g_Watches[i]) {
      if (playerinfo(i,Name,MAX_NAME_LENGTH)) {
        if (fFirst) {
          language_say(UserIndex,"### The following players should be watched:",print_type:print_console,0,0,0,0);
          fFirst=0;
        }
        language_sayf(UserIndex,"###  - %1s",print_type:print_console,0,0,0,0,Name);
      }
    }
  }
  if (fFirst) {
    language_sayf(UserIndex,"### No players currently on the server need to be watched.",print_type:print_console,0,0,0,0);
  }
 
}
 
ReportLan(UserIndex) {
  new i;
  new c=maxplayercount();
  new strName[MAX_NAME_LENGTH];
 
  for (i=1;i<=c;i++) {
    g_LanDetect[i]=0;
  }
 
  for (i=1;i<=c;i++) {
    if (g_LanDetect[i]==0) {
      if (playerinfo(i,strName,MAX_NAME_LENGTH)) {
        CheckIP(i,UserIndex);
      }
    }
  }
 
  for (i=1;i<=c;i++) {
    if (g_LanDetect[i]==0) {
      if (playerinfo(i,strName,MAX_NAME_LENGTH)) {
        CheckSubnet(i,UserIndex);
      }
    }
  }
 
  for (i=1;i<=c;i++) {
    if (g_LanDetect[i]==1) {
      return;
    }
  }
 
  language_sayf(UserIndex,"### No players currently detected on the same IP address or subnet.",print_type:print_console,0,0,0,0);
}
 
ReportAdmins(UserIndex) {
  new c=maxplayercount();
  new i;
  new j;
  new fFirst=1;
  new Name[MAX_NAME_LENGTH];
  new level;
  new test;
  new def = getvar("default_access");
 
  for(i=1;i<=c;i++) {
    if (playerinfo(i,Name,MAX_NAME_LENGTH)) {
 
      /* Cant report on the top-most access level because it goes negative! */
      level=0;
      test=1;
      for (j=0;j<31;j++) {
        if (access(test,Name)) {
          level=level+test;
        }
        test=test*2;
      }
 
      if ((level>0) && (level!=def)) {
        if (fFirst) {
          language_say(UserIndex,"### The following players have administrator access:",print_type:print_console,0,0,0,0);
          fFirst=0;
        }
        language_sayf(UserIndex,"###  - %1s (%2i)",print_type:print_console,0,0,0,0,Name,level);
      }
    }
  }
  if (fFirst) {
    language_sayf(UserIndex,"### No players currently on the server have administrator access.",print_type:print_console,0,0,0,0);
  }
 
}
 
 
ReportHeader(UserIndex,AuthID[],Online,AKA[],fFull) {
  /* Report the WONID and time connected */
  /* Report the number of connections today, last 7 days, last 30 days, total */
  /* Report the kills with/without admin */
 
  /* Schedule rest of report to occur in a few seconds to prevent overflowing the admin's client */
  if (fFull) {
	set_timer("ReportNames",1,1,AuthID);
  	set_timer("ReportIPs",2,1,AuthID);
  	set_timer("ReportOtherIDs",3,1,AuthID);
  	set_timer("ReportNotes",4,1,AuthID);
  }
  else {
	set_timer("ReportNamesShort",1,1,AuthID);
	set_timer("ReportNotes",2,1,AuthID);
  }
 
 
  new TargetIndex=-1;
  if (Online>0) {
    TargetIndex=get_indexFromAuthID(AuthID);
  }
 
  new K[4];
  new D[4];
 
  new Path[MAX_DATA_LENGTH];
  new ParsedAuthID[MAX_AUTHID_LENGTH];
  new HashParsedAuthID;
  GenerateFilename(Path,ParsedAuthID,HashParsedAuthID,"addons/whois/db",AuthID,"Kills.xml");
  LoadKillStatus(Path,K,D);
 
  if (Online>0) {
    K[KD_NOCD_ADMIN]+=g_Kills[TargetIndex][KD_NOCD_ADMIN];
    K[KD_NOCD_NOADMIN]+=g_Kills[TargetIndex][KD_NOCD_NOADMIN];
    K[KD_CD_ADMIN]+=g_Kills[TargetIndex][KD_CD_ADMIN];
    K[KD_CD_NOADMIN]+=g_Kills[TargetIndex][KD_CD_NOADMIN];
    D[KD_NOCD_ADMIN]+=g_Deaths[TargetIndex][KD_NOCD_ADMIN];
    D[KD_NOCD_NOADMIN]+=g_Deaths[TargetIndex][KD_NOCD_NOADMIN];
    D[KD_CD_ADMIN]+=g_Deaths[TargetIndex][KD_CD_ADMIN];
    D[KD_CD_NOADMIN]+=g_Deaths[TargetIndex][KD_CD_NOADMIN];
  }
 
 
  new Ratio1[20];
  new Ratio2[20];
  new Ratio3[20];
  new Ratio4[20];
  new Ratio5[20];
 
  CalcRatio(K[KD_NOCD_ADMIN],D[KD_NOCD_ADMIN],Ratio1);
  CalcRatio(K[KD_NOCD_NOADMIN],D[KD_NOCD_NOADMIN],Ratio2);
  CalcRatio(K[KD_CD_ADMIN],D[KD_CD_ADMIN],Ratio3);
  CalcRatio(K[KD_CD_NOADMIN],D[KD_CD_NOADMIN],Ratio4);
  CalcRatio(K[KD_NOCD_NOADMIN]+K[KD_NOCD_ADMIN]+K[KD_CD_NOADMIN]+K[KD_CD_ADMIN],
            D[KD_NOCD_NOADMIN]+D[KD_NOCD_ADMIN]+D[KD_CD_NOADMIN]+D[KD_CD_ADMIN],Ratio5);
 
 
  new TopName[MAX_NAME_LENGTH];
  new CurName[MAX_NAME_LENGTH];
  new swap;
  new level=0;
 
  if (Online>0) {
    playerinfo(TargetIndex,CurName,MAX_NAME_LENGTH);
 
    new test=1;
    new i;
    for (i=0;i<31;i++) {
      if (access(test,CurName)) {
        level=level+test;
      }
      test=test*2;
    }
 
    RemoveCD(CurName);
 
  }
  else {
    CurName[0]=0;
  }
 
  if (strlen(AKA)==0) {
    GetTopName(AuthID,CurName,TopName,swap);
  }
 
 
  if (Online>0) {
    /* Cant report on the top-most access level because it goes negative! */
  }
 
  new strIP[20];
  if (Online>0) {
    iptostr(g_IPs[TargetIndex],strIP);
  }
  else {
    strcpy(strIP,"Not connected",20);
  }
 
  if (strlen(AKA)==0) {
    language_sayf(UserIndex,"### AuthID %1s - Name ^"%2s^" - Aka ^"%3s^"",print_type:print_console,0,0,0,0,AuthID,CurName,TopName);
  }
  else {
    language_sayf(UserIndex,"### AuthID %1s - Name ^"%2s^" - Aka ^"%3s^"",print_type:print_console,0,0,0,0,AuthID,CurName,AKA);
  }
 
  if (Online>0) {
    if (level==getvar("default_access")) {
      language_sayf(UserIndex,"### IP %1s - Admin Level %2i (default)",print_type:print_console,0,0,0,0,strIP,level);
    }
    else {
      language_sayf(UserIndex,"### IP %1s - Admin Level %2i",print_type:print_console,0,0,0,0,strIP,level);
    }
  }
 
  if (fFull) {
    if ((K[KD_NOCD_ADMIN]==0) || (D[KD_NOCD_ADMIN]==0)) {
      language_sayf(UserIndex,"### No C-D Administered: %1i kills, %2i deaths",print_type:print_console,0,0,0,0,K[KD_NOCD_ADMIN],D[KD_NOCD_ADMIN]);
    }
    else {
      language_sayf(UserIndex,"### No C-D Administered: %1i kills, %2i deaths, %3s ratio",print_type:print_console,0,0,0,0,K[KD_NOCD_ADMIN],D[KD_NOCD_ADMIN],Ratio1);
    }
    if ((K[KD_NOCD_NOADMIN]==0) || (D[KD_NOCD_NOADMIN]==0)) {
      language_sayf(UserIndex,"### No C-D Unmonitored:  %1i kills, %2i deaths",print_type:print_console,0,0,0,0,K[KD_NOCD_NOADMIN],D[KD_NOCD_NOADMIN]);
    }
    else {
      language_sayf(UserIndex,"### No C-D Unmonitored:  %1i kills, %2i deaths, %3s ratio",print_type:print_console,0,0,0,0,K[KD_NOCD_NOADMIN],D[KD_NOCD_NOADMIN],Ratio2);
    }
    if ((K[KD_CD_ADMIN]==0) || (D[KD_CD_ADMIN]==0)) {
      language_sayf(UserIndex,"### C-D Administered:    %1i kills, %2i deaths",print_type:print_console,0,0,0,0,K[KD_CD_ADMIN],D[KD_CD_ADMIN]);
    }
    else {
      language_sayf(UserIndex,"### C-D Administered:    %1i kills, %2i deaths, %3s ratio",print_type:print_console,0,0,0,0,K[KD_CD_ADMIN],D[KD_CD_ADMIN],Ratio3);
    }
    if ((K[KD_CD_NOADMIN]==0) || (D[KD_CD_NOADMIN]==0)) {
      language_sayf(UserIndex,"### C-D Unmonitored:     %1i kills, %2i deaths",print_type:print_console,0,0,0,0,K[KD_CD_NOADMIN],D[KD_CD_NOADMIN]);
    }
    else {
      language_sayf(UserIndex,"### C-D Unmonitored:     %1i kills, %2i deaths, %3s ratio",print_type:print_console,0,0,0,0,K[KD_CD_NOADMIN],D[KD_CD_NOADMIN],Ratio4);
    }
  }
 
  if ((K[KD_NOCD_NOADMIN]+K[KD_NOCD_ADMIN]+K[KD_CD_NOADMIN]+K[KD_CD_ADMIN]==0) || (D[KD_NOCD_NOADMIN]+D[KD_NOCD_ADMIN]+D[KD_CD_NOADMIN]+D[KD_CD_ADMIN]==0)) {
    language_sayf(UserIndex,"### Overall K:D ratio:    %1i kills, %2i deaths",print_type:print_console,0,0,0,0,K[KD_NOCD_NOADMIN]+K[KD_NOCD_ADMIN]+K[KD_CD_NOADMIN]+K[KD_CD_ADMIN],D[KD_NOCD_NOADMIN]+D[KD_NOCD_ADMIN]+D[KD_CD_NOADMIN]+D[KD_CD_ADMIN]);
  }
  else {
    language_sayf(UserIndex,"### Overall K:D ratio:    %1i kills, %2i deaths, %3s ratio",print_type:print_console,0,0,0,0,K[KD_NOCD_NOADMIN]+K[KD_NOCD_ADMIN]+K[KD_CD_NOADMIN]+K[KD_CD_ADMIN],D[KD_NOCD_NOADMIN]+D[KD_NOCD_ADMIN]+D[KD_CD_NOADMIN]+D[KD_CD_ADMIN],Ratio5);
  }
 
  ReportConnections(UserIndex,AuthID);
}
 
ReportConnections(UserIndex,AuthID[]) {
  new i=4;
  new strFDate[MAX_DATE_LENGTH];
  new strFConnects[MAX_NUMBER_LENGTH];
  new Path[MAX_DATA_LENGTH];
  new ParsedAuthID[MAX_AUTHID_LENGTH];
  new HashParsedAuthID;
  GenerateFilename(Path,ParsedAuthID,HashParsedAuthID,"addons/whois/db",AuthID,"Connections.xml");
 
  new fs = filesize(Path);
 
  new year;
  new month;
  new day;
  new hour;
  new minute;
  new second;
  get_dateandtime(year,month,day,hour,minute,second);
 
  new fyear;
  new fmonth;
  new fday;
 
  new nDay=0;
  new nWeek=0;
  new nMonth=0;
  new nOverall=0;
  new nDiff;
  new nConnects;
 
  new fFirst=1;
 
  while(i<fs) {
    readfile(Path,strFDate,i+2,MAX_DATA_LENGTH);
    readfile(Path,strFConnects,i+5,MAX_NUMBER_LENGTH);
    SplitDate(strFDate,fyear,fmonth,fday);
    nDiff = DaysDiff(year,month,day,fyear,fmonth,fday);
    if (fFirst==1) {
      fFirst=0;
      language_sayf(UserIndex,"### Total Rounds Played %4i    First Connection: %1i %2s %3i",print_type:print_console,0,0,0,0,fday,g_monthnames[fmonth-1],fyear,LookupRoundCount(AuthID));
    }
    nConnects=strtonum(strFConnects);
    if (nDiff==0) {
      nDay+=nConnects;
    }
    if (nDiff <7) {
      nWeek+=nConnects;
    }
    if (nDiff <30) {
      nMonth+=nConnects;
    }
    nOverall+=nConnects;
    i=i+8;
  }
  language_sayf(UserIndex,"### Connections %4i (%1i today, %2i this week, %3i this month)",print_type:print_console,0,0,0,0,nDay,nWeek,nMonth,nOverall);
}
 
 
public ReportNames(Timer,Repeat,HLUserName,HLParam) {
  /* Report top 10 names in order of use */
  new AuthID[MAX_DATA_LENGTH];
  new User[MAX_NAME_LENGTH];
 
  convert_string(HLUserName,User,MAX_NAME_LENGTH);
  convert_string(HLParam,AuthID,MAX_DATA_LENGTH);
 
  ReportTop10(User,AuthID,"Names","###^n### Known Aliases:",10);
  return PLUGIN_HANDLED;
}
 
public ReportNamesShort(Timer,Repeat,HLUserName,HLParam) {
  /* Report top 10 names in order of use */
  new AuthID[MAX_DATA_LENGTH];
  new User[MAX_NAME_LENGTH];
 
  convert_string(HLUserName,User,MAX_NAME_LENGTH);
  convert_string(HLParam,AuthID,MAX_DATA_LENGTH);
 
  ReportTop10(User,AuthID,"Names","###^n### Known Aliases:",3);
  return PLUGIN_HANDLED;
}
 
public ReportIPs(Timer,Repeat,HLUserName,HLParam) {
  /* Report top 10 IP addresses in order of use */
  new AuthID[MAX_DATA_LENGTH];
  new User[MAX_NAME_LENGTH];
 
  convert_string(HLUserName,User,MAX_NAME_LENGTH);
  convert_string(HLParam,AuthID,MAX_DATA_LENGTH);
 
  ReportTop10(User,AuthID,"IPs","###^n### Known IP Addresses:",10);
  return PLUGIN_HANDLED;
}
 
 
ReportTop10(User[],AuthID[],strFile[],strPrompt[],nCount) {
  /* Names & Rounds are a linked list. Head points to the first
   * element, and Next[e] points to the next element, with -1
   * marking the end.  alloc tracks how many rows are used for
   * the list
   *
   * This data structure means lines are sorted efficiently
   * as the file is loaded */
 
  new Names[10][MAX_NAME_LENGTH];
  new Rounds[10];
  new Next[10];
 
  new pos;
  new head=-1;
  new alloc=0;
  new last=-1;
  new i;
 
  for(i=0;i<nCount;i++) {
    Next[i]=-1;
  }
 
  new Path[MAX_DATA_LENGTH];
  new ParsedAuthID[MAX_AUTHID_LENGTH];
  new HashParsedAuthID;
 
  new strFullFile[MAX_DATA_LENGTH];
  strcpy(strFullFile,strFile,MAX_DATA_LENGTH);
  strcat(strFullFile,".xml",MAX_DATA_LENGTH);
  GenerateFilename(Path,ParsedAuthID,HashParsedAuthID,"addons/whois/db",AuthID,strFullFile);
 
  new fs = filesize(Path);
  if (fs<=0) {
    return PLUGIN_HANDLED;
  }
  new strFName[MAX_NAME_LENGTH];
  new strFRounds[MAX_NUMBER_LENGTH];
  new nRounds;
 
  i=4;
  while(i<fs) {
    readfile(Path,strFName,i+2,MAX_NAME_LENGTH);
    readfile(Path,strFRounds,i+5,MAX_NUMBER_LENGTH);
    i=i+8;
    nRounds=strtonum(strFRounds);
    pos=head;
    last=-1;
    /* Try and find the right place for the new entry in the list */
    while(pos!=-1) {
      if (Rounds[pos]<nRounds) {
        /* Insert before pos */
        if (alloc<nCount) {
          /* There are free slots for an insert so do so. */
          strcpy(Names[alloc],strFName,MAX_NAME_LENGTH);
          Rounds[alloc]=nRounds;
          Next[alloc]=pos;
          if (pos==head) {
            head=alloc;
          } else {
            Next[last]=alloc;
          }
          alloc++;
          break;
        }
        else {
          /* There are no free slots, so locate the end of the list and
           * overwrite it */
          new pos2=pos;
          new pos3=pos;
          while (Next[pos2]!=-1) { pos3=pos2;pos2=Next[pos2]; }
          if (pos2!=pos3) {
            /* New line being inserted before some entry that isn't
             * the end one. pos = entry we are inserting before,
             * pos2 = end entry, pos3 = 2nd from end entry,
             * last = entry we are inserting after */
            Next[pos3]=-1;
            strcpy(Names[pos2],strFName,MAX_NAME_LENGTH);
            Rounds[pos2]=nRounds;
            Next[pos2]=pos;
            if (pos==head) {
              head=pos2;
            }
            else {
              Next[last]=pos2;
            }
          }
          else {
            /* New line is being inserted before the end entry, therefore
             * simply overwrite the end entry with it */
            strcpy(Names[pos],strFName,MAX_NAME_LENGTH);
            Rounds[pos]=nRounds;
          }
          break;
        }
 
      }
      else {
        /* New line has a smaller number of rounds than line pos - keep
         * looking */
        last=pos;
        pos=Next[pos];
      }
    }
    if (pos==-1) {
      /* the previous loop reached the end of the list without inserting
       * so append if there is room */
      if (alloc<nCount) {
        strcpy(Names[alloc],strFName,MAX_NAME_LENGTH);
        Rounds[alloc]=nRounds;
        Next[alloc]=-1;
        if (last==-1) {
          head=alloc;
        } else {
          Next[last]=alloc;
        }
        alloc++;
      }
      else {
        /* list is full, loose this entry as it has a smaller number of
         * rounds than all those in the list */
      }
    }
  }
 
  pos=head;
  language_saybynamef(User,strPrompt,print_type:print_console,0,0,0,0);
  while(pos!=-1) {
    language_saybynamef(User,"###   %1s (%2i rounds)",print_type:print_console,0,0,0,0,Names[pos],Rounds[pos]);
    pos=Next[pos];
 
  }
  return 0;
}
 
public ReportOtherIDs(Timer,Repeat,HLUserName,HLParam) {
  new AuthID[MAX_DATA_LENGTH];
  new User[MAX_NAME_LENGTH];
 
  convert_string(HLUserName,User,MAX_NAME_LENGTH);
  convert_string(HLParam,AuthID,MAX_DATA_LENGTH);
 
  new TargetIndex;
  get_userindex(AuthID,TargetIndex);
 
  new IP[30];
  new Subnet[30];
  iptostr(g_IPs[TargetIndex],IP);
  iptostr(g_IPs[TargetIndex] & 4294967040,Subnet); /*4294967040 = 0xFFFFFF00 - masks out last octet */
 
  new Path[MAX_DATA_LENGTH];
  new ParsedAuthID[MAX_AUTHID_LENGTH];
  new HashParsedAuthID;
  GenerateFilename(Path,ParsedAuthID,HashParsedAuthID,"addons/whois/ips",IP,"address.txt");
 
  new fPrompt=1;
  new fs = filesize(Path);
  new strFAuthID[MAX_AUTHID_LENGTH];
  new i=1;
  while (i<fs) {
    readfile(Path,strFAuthID,i,MAX_AUTHID_LENGTH);
    if (streq(strFAuthID,AuthID)==0) {
      if (fPrompt==1) {
        fPrompt=0;
        language_saybyname(User,"###^n### Other players using this Subnet/IPAddress:",print_type:print_console,0,0,0,0);
      }
 
      new strTopName[MAX_NAME_LENGTH];
      new dummy;
      GetTopName(strFAuthID,"",strTopName,dummy);
      language_saybynamef(User,"###   %1s (%2s) played from the same address",print_type:print_console,0,0,0,0,strTopName,strFAuthID);
    }
    i++;
  }
 
  new SubnetPath[MAX_DATA_LENGTH];
  GenerateFilename(Path,ParsedAuthID,HashParsedAuthID,"addons/whois/ips",Subnet,"_subnet.txt");
  fs = filesize(SubnetPath);
  i=1;
  while (i<fs) {
    readfile(SubnetPath,strFAuthID,i,MAX_AUTHID_LENGTH);
    if (streq(strFAuthID,AuthID)==0) {
      if (LookupAddress(Path,strFAuthID)==0) {
        if (fPrompt==1) {
          fPrompt=0;
          language_saybyname(User,"###^n### Other players using this Subnet/IPAddress:",print_type:print_console,0,0,0,0);
        }
 
        new strTopName[MAX_NAME_LENGTH];
        new dummy;
        GetTopName(strFAuthID,"",strTopName,dummy);
        language_saybynamef(User,"###   %1s (%2s) played from the same subnet",print_type:print_console,0,0,0,0,strTopName,strFAuthID);
      }
    }
    i++;
  }
 
  return PLUGIN_HANDLED;
}
 
 
public ReportNotes(Timer,Repeat,HLUserName,HLParam) {
  new AuthID[MAX_DATA_LENGTH];
  new User[MAX_NAME_LENGTH];
  new strLine[MAX_DATA_LENGTH];
 
  convert_string(HLUserName,User,MAX_NAME_LENGTH);
  convert_string(HLParam,AuthID,MAX_DATA_LENGTH);
 
  new Path[MAX_DATA_LENGTH];
  new ParsedAuthID[MAX_AUTHID_LENGTH];
  new HashParsedAuthID;
  GenerateFilename(Path,ParsedAuthID,HashParsedAuthID,"addons/whois/db",AuthID,"Notes.xml");
 
  new date[MAX_DATE_LENGTH];
  new type[MAX_DATA_LENGTH];
  new adminauth[MAX_AUTHID_LENGTH];
  new adminname[MAX_NAME_LENGTH];
  new msg[MAX_DATA_LENGTH];
  new data[MAX_DATA_LENGTH];
  new dummy[MAX_DATA_LENGTH];
 
  new fs = filesize(Path)-1;
  if (fs>0) {
    language_saybyname(User,"###^n### Recent notes / punishments:",print_type:print_console,0,0,0,0);
    new i=fs-169;
    if (i<4) { i=4; }
    while(i<=fs) {
      readfile(Path,date,i+2,MAX_DATE_LENGTH);
      readfile(Path,type,i+5,MAX_DATA_LENGTH);
      readfile(Path,strLine,i+7,MAX_DATA_LENGTH);
      readfile(Path,adminname,i+8,MAX_NAME_LENGTH);
      readfile(Path,msg,i+11,MAX_DATA_LENGTH);
      readfile(Path,data,i+14,MAX_DATA_LENGTH);
      strsplitx(strLine,'^"','|',dummy,MAX_DATA_LENGTH,adminauth,MAX_DATA_LENGTH,dummy,MAX_DATA_LENGTH);
      if (strlen(type)>0) {
        if (streq(type,"Ban")) {
          language_saybynamef(User,"###   %1s: %2s by %3s(%4s) for %5s minutes - %6s",print_type:print_console,0,0,0,0,date,type,adminname,adminauth,data, msg);
        }
        else {
          if (streq(type,"Team Kill")) {
            language_saybynamef(User,"###   %1s: %2s %3s - %4s",print_type:print_console,0,0,0,0,date,type,data,msg);
          }
          else {
            language_saybynamef(User,"###   %1s: %2s by %3s(%4s) - %5s",print_type:print_console,0,0,0,0,date,type,adminname,adminauth,msg);
          }
        }
      }
      i=i+17;
    }
  }
  return PLUGIN_HANDLED;
}
 
 
/* If strCurName is the most commonly used name returns
 * the 2nd most commonly used or empty string and second is 1
 *
 * If strCurName is not the most commonly used name,
 * returns the most commonly used and second is 0
 */
GetTopName(AuthID[],strCurName[],strTopName[],&Second)
{
  new i=4;
  new strFName[MAX_NAME_LENGTH];
  new strFRounds[MAX_NUMBER_LENGTH];
  new Path[MAX_DATA_LENGTH];
  new ParsedAuthID[MAX_AUTHID_LENGTH];
  new HashParsedAuthID;
  GenerateFilename(Path,ParsedAuthID,HashParsedAuthID,"addons/whois/db",AuthID,"Names.xml");
 
  new fs = filesize(Path);
  new nRounds;
  new nTopRounds=0;
  new nCurRounds=0;
 
  strcpy(strTopName,strCurName,MAX_NAME_LENGTH);
 
  while(i<fs) {
    readfile(Path,strFName,i+2,MAX_DATA_LENGTH);
    readfile(Path,strFRounds,i+5,MAX_DATA_LENGTH);
    nRounds=strtonum(strFRounds);
    if (streq(strCurName,strFName)) {
      nCurRounds = nRounds;
    }
    else {
      if (nRounds > nTopRounds) {
        strcpy(strTopName,strFName,MAX_NAME_LENGTH);
        nTopRounds=nRounds;
      }
    }
    i=i+8;
  }
 
  if (nCurRounds>nTopRounds) {
    Second=1;
  }
  else {
    Second=0;
  }
 
  /* If we have no recorded name, and no current
   * name, get the name the user is currently playing as */
  if ((strTopName[0]==0) && (strCurName[0]==0)) {
    get_username(AuthID,strTopName,MAX_NAME_LENGTH);
    RemoveCD(strTopName);
  }
 
}
 
/* *************** General Support ***************** */
 
 
Resolve(Data[],AuthID[],&TargetIndex,TargetName[],AKA[]) {
  new temp[MAX_NAME_LENGTH];
 
  TargetIndex=0;
  AKA[0]=0;
 
  if (get_userAuthID(Data,AuthID,MAX_AUTHID_LENGTH)) {
    /* Player found, report on them */
    get_userindex(Data,TargetIndex);
    get_username(Data,temp,MAX_NAME_LENGTH);
    RemoveCD(temp);
    strcpy(TargetName,temp,MAX_NAME_LENGTH);
    return 1;
  }
  else {
    /* Player not found, check on the last MAX_OLD_PLAYERS people to leave in this map
     * starting with the last to leave for a substring match */
    new i=g_OldPtr-1;
    while (i>=0) {
      if (strcasestrx(g_OldNames[i],Data)>=0) {
        strcpy(AuthID,g_OldAuthIDs[i],MAX_AUTHID_LENGTH);
        strcpy(TargetName,g_OldNames[i],MAX_NAME_LENGTH);
 
        /* Player may still be connected and just changed their name
         * try and match the authid looked up against connected players */
        new j;
    	for(j=0;j<MAX_PLAYERS;j++) {
          if (streq(g_AuthIDs[j],AuthID)) {
            strcpy(AKA,g_OldNames[i],MAX_NAME_LENGTH);
 
            /* Have to use temp array due to compiler bug in writing direct to targetname */
            TargetIndex=j;
            if (playerinfo(TargetIndex,temp,MAX_NAME_LENGTH)) {
              RemoveCD(temp);
              strcpy(TargetName,temp,MAX_NAME_LENGTH);
            }
            return 1;
    	  }
    	}
        return 1;
      }
      i--;
    }
 
    /* OK, still no match - lets try and match it as an authid */
    new Path[MAX_DATA_LENGTH];
    new ParsedAuthID[MAX_AUTHID_LENGTH];
    new HashParsedAuthID;
    GenerateFilename(Path,ParsedAuthID,HashParsedAuthID,"addons/whois/db",AuthID,"Kills.xml");
    if (fileexists(Path)) {
      strcpy(AuthID,Data,MAX_AUTHID_LENGTH);
      new DummyName[MAX_NAME_LENGTH];
      new Dummy;
      GetTopName(AuthID,DummyName,TargetName,Dummy);
      return 1;
    }
  }
 
  return 0;
}
 
IsAdminConnected() {
  new c=maxplayercount();
  new i;
  new name[MAX_NAME_LENGTH];
 
  for (i=1;i<=c;i++) {
    if (playerinfo(i,name,MAX_NAME_LENGTH)) {
      if (access(ACCESS_KICK,name)) {
        return 1;
      }
    }
  }
  return 0;
}
 
Hash(strin[]) {
  new c = strlen(strin);
  new h = 1;
  new i;
 
  for(i=0;i<c;i++) {
    h = ((h*strin[i]) % 99)+1;
  }
  return h;
}
 
GetDate(strDate[]) {
  new year;
  new month;
  new day;
  new hour;
  new minute;
  new second;
  get_dateandtime(year,month,day,hour,minute,second);
  snprintf(strDate,MAX_DATE_LENGTH,"%i-%i-%i",year,month,day);
}
 
GetAuthID(UserIndex,AuthID[MAX_AUTHID_LENGTH]) {
  new Name[MAX_NAME_LENGTH];
  new Wonid;
  new Sessionid;
  new Team;
  new Deaths;
 
  if (g_AuthIDs[UserIndex][0]==0) {
    if (playerinfo(UserIndex,Name,MAX_NAME_LENGTH,Sessionid,Wonid,Team,Deaths,AuthID)==0) {
      return 0;
    }
    strcpy(g_AuthIDs[UserIndex],AuthID,MAX_AUTHID_LENGTH);
  }
  else {
    strcpy(AuthID,g_AuthIDs[UserIndex],MAX_AUTHID_LENGTH);
  }
  return 1;
}
 
SplitDate(strDate[],&year,&month,&day) {
   new strYear[MAX_NUMBER_LENGTH];
   new strMonth[MAX_NUMBER_LENGTH];
   new strDay[MAX_NUMBER_LENGTH];
   strsplitx(strDate,'-','\',strYear,MAX_NUMBER_LENGTH,strMonth,MAX_NUMBER_LENGTH,strDay,MAX_NUMBER_LENGTH);
   year=strtonum(strYear);
   month=strtonum(strMonth);
   day=strtonum(strDay);
}
 
DaysDiff(year1,month1,day1,year2,month2,day2) {
  return getDaysSince1970(year1,month1,day1)-getDaysSince1970(year2,month2,day2);
}
 
 
CheckCrossReference(TargetIndex) {
  new AuthID[MAX_AUTHID_LENGTH];
  GetAuthID(TargetIndex,AuthID);
 
  new IP[30];
  new Subnet[30];
  iptostr(g_IPs[TargetIndex],IP);
  iptostr(g_IPs[TargetIndex] & 4294967040,Subnet); /*4294967040 = 0xFFFFFF00 - masks out last octet */
 
  new Path[MAX_DATA_LENGTH];
  new ParsedAuthID[MAX_AUTHID_LENGTH];
  new HashParsedAuthID;
  GenerateFilename(Path,ParsedAuthID,HashParsedAuthID,"addons/whois/ips",IP,"_address.txt");
  if (CheckIPFile(TargetIndex,Path,AuthID)) {
    return 1;
  }
  GenerateFilename(Path,ParsedAuthID,HashParsedAuthID,"addons/whois/ips",Subnet,"_subnet.txt");
  if (CheckIPFile(TargetIndex,Path,AuthID)) {
    return 1;
  }
  return 0;
}
 
CheckIPFile(TargetIndex,Path[],AuthID[]) {
  new fs = filesize(Path);
  new strFAuthID[MAX_AUTHID_LENGTH];
  new i=1;
  while (i<fs) {
    readfile(Path,strFAuthID,i,MAX_AUTHID_LENGTH);
    if (streq(strFAuthID,AuthID)==0) {
 
      new Buf[MAX_DATA_LENGTH];
      new WatchPath[MAX_DATA_LENGTH];
      new ParsedAuthID[MAX_AUTHID_LENGTH];
      new HashParsedAuthID;
      GenerateFilename(WatchPath,ParsedAuthID,HashParsedAuthID,"addons/whois/db",strFAuthID,"Notes.xml");
      if (fileexists(WatchPath)) {
        new fs2= filesize(WatchPath)-1;
        if (fs2>0) {
          new j=4;
          new type[20];
          while(j<=fs2) {
            readfile(WatchPath,type,j+5,MAX_DATA_LENGTH);
            if (streq(type,"Ban")) {
              readfile(WatchPath,type,i+14,MAX_DATA_LENGTH);
              if (streq(type,"0")) {
                CheckIPSetWatch(TargetIndex,AuthID,strFAuthID,"/has been banned");
              }
              return 1;
            }
            j=j+17;
          }
        }
      }
 
      GenerateFilename(WatchPath,ParsedAuthID,HashParsedAuthID,"addons/whois/db",strFAuthID,".xml");
      if (fileexists(WatchPath)) {
        readfile(WatchPath,Buf,5,MAX_DATA_LENGTH);
        if (strtonum(Buf)) {
          CheckIPSetWatch(TargetIndex,AuthID,strFAuthID,"watched");
          return 1;
        }
      }
 
 
    }
    i++;
  }
 
  return 0;
}
 
CheckIPSetWatch(TargetIndex,AuthID[],DupAuthID[],Reason[]) {
 
  if (HasBeenUnwatched(AuthID)) {
    return 0;
  }
 
  /* Record connecting player as to-be-watched */
  new strTopName[MAX_NAME_LENGTH];
  new dummy;
  new msg[MAX_DATA_LENGTH];
 
  GetTopName(DupAuthID,"",strTopName,dummy);
 
  g_Watches[TargetIndex]=1;
  WriteSettings(TargetIndex);
 
  snprintf(msg,MAX_DATA_LENGTH,"Playing from same IP/Subnet as %s(%s) who is %s",strTopName,DupAuthID,Reason);
  LogNote("Watch",AuthID,0,"Automatic",msg,"",0);
 
  return 1;
}
 
 
HasBeenUnwatched(AuthID[]) {
  /* Check if the connecting player has already been unwatched. If so
   * don't watch them again */
  new type[20];
 
  new Path[MAX_DATA_LENGTH];
  new ParsedAuthID[MAX_DATA_LENGTH];
  new HashParsedAuthID;
  GenerateFilename(Path,ParsedAuthID,HashParsedAuthID,"addons/whois/db",AuthID,"Notes.xml");
 
  if (fileexists(Path)) {
    new fs= filesize(Path)-1;
    if (fs>0) {
      new i=4;
      while(i<=fs) {
        readfile(Path,type,i+5,MAX_DATA_LENGTH);
        if (streq(type,"Unwatch")) {
          return 1;
        }
        i=i+17;
      }
    }
  }
  return 0;
}
 
CalcRatio(K,D,Ratio[]) {
  new m;
  new n;
 
  if ((K==0) || (D==0)) {
    strcpy(Ratio,"N/A",4);
  }
  else {
    m = (((K * 10) / D) + 5) / 10;
    if (m>2) {
      n = 1;
    }
    else {
      n = (((D * 10) / K) + 5) / 10;
      if (n>2) {
        m=1;
      }
      else {
        if (K==D) {
          m = 1;
          n = 1;
        }
        else {
          if (K>D) {
            m = (K * 10) / D;
            n = 10;
          }
          else {
            n = (D * 10) / K;
            m = 10;
          }
        }
      }
    }
 
    snprintf(Ratio,20,"%i:%i",m,n);
  }
}
 
 
CheckIP(UserIndex,AdminIndex) {
  new j=0;
  new strUsers[MAX_DATA_LENGTH];
  new strName[MAX_NAME_LENGTH];
  new c=maxplayercount();
  for (j=1;j<=c;j++) {
    if (j!=UserIndex) {
      if (playerinfo(j,strName,MAX_NAME_LENGTH)) {
        if (g_IPs[UserIndex]==g_IPs[j]) {
          if (strUsers[0]==0) {
            strcpy(strUsers,strName,MAX_DATA_LENGTH);
          }
          else {
            strcat(strUsers,", ",MAX_DATA_LENGTH);
            strcat(strUsers,strName,MAX_DATA_LENGTH);
          }
          g_LanDetect[UserIndex]=1;
          g_LanDetect[j]=1;
        }
      }
    }
  }
 
  if (strUsers[0]) {
    playerinfo(UserIndex,strName,MAX_NAME_LENGTH);
    new strAdmin[MAX_NAME_LENGTH];
    if (AdminIndex==0) {
      /* Announce to all admins */
      for(j=1;j<c;j++) {
        if (playerinfo(j,strAdmin,MAX_NAME_LENGTH)) {
          if (access(ACCESS_WHOIS,strAdmin)) {
            language_saybynamef(strAdmin,"### %1s and %2s are playing from the same IP address.",print_type:print_console,0,0,0,0,strUsers,strName);
            language_saybynamef(strAdmin,"### %1s and %2s are playing from the same IP address.",print_type:print_chat,0,0,0,0,strUsers,strName);
          }
        }
      }
    }
    else {
      language_sayf(AdminIndex,"### %1s and %2s are playing from the same IP address.",print_type:print_console,0,0,0,0,strUsers,strName);
    }
  }
 
}
 
 
CheckSubnet(UserIndex,AdminIndex) {
  new j=0;
  new strUsers[MAX_DATA_LENGTH];
  new strName[MAX_NAME_LENGTH];
  new c=maxplayercount();
  for (j=1;j<=c;j++) {
    if (j!=UserIndex) {
      if (playerinfo(j,strName,MAX_NAME_LENGTH)) {
        if ((g_IPs[UserIndex] & 0xFFFFFF00) == (g_IPs[j] & 0xFFFFFF00)) {
          if (strUsers[0]==0) {
            strcpy(strUsers,strName,MAX_DATA_LENGTH);
          }
          else {
            strcat(strUsers,", ",MAX_DATA_LENGTH);
            strcat(strUsers,strName,MAX_DATA_LENGTH);
          }
          g_LanDetect[UserIndex]=1;
          g_LanDetect[j]=1;
        }
      }
    }
  }
 
  if (strUsers[0]) {
    playerinfo(UserIndex,strName,MAX_NAME_LENGTH);
    new strAdmin[MAX_NAME_LENGTH];
    if (AdminIndex==0) {
      /* Announce to all admins */
      for(j=1;j<c;j++) {
        if (playerinfo(j,strAdmin,MAX_NAME_LENGTH)) {
          if (access(ACCESS_WHOIS,strAdmin)) {
            language_saybynamef(strAdmin,"### %1s and %2s are playing from the same subnet.",print_type:print_console,0,0,0,0,strUsers,strName);
            language_saybynamef(strAdmin,"### %1s and %2s are playing from the same subnet.",print_type:print_chat,0,0,0,0,strUsers,strName);
          }
        }
      }
    }
    else {
      language_sayf(AdminIndex,"### %1s and %2s are playing from the same IP subnet.",print_type:print_console,0,0,0,0,strUsers,strName);
    }
  }
 
}
 
CheckOldNames(NewName[],AuthID[]) {
  new i=g_OldPtr-1;
  new msg[MAX_TEXT_LENGTH];
 
  while (i>0) {
    if (streq(AuthID,g_OldAuthIDs[i])) {
      if (streq(NewName,g_OldNames[i])==0) {
        if (g_OldKicked[i]==-12345678) {
          snprintf(msg,MAX_TEXT_LENGTH,"### %s who was kicked earlier has rejoined as %s",g_OldNames[i],NewName);
        }
        else if (g_OldKicked[i]>0) {
          snprintf(msg,MAX_TEXT_LENGTH,"### %s who was banned earlier for %i minutes has rejoined as %s",g_OldNames[i],g_OldKicked[i],NewName);
        }
        else {
          snprintf(msg,MAX_TEXT_LENGTH,"### %s has rejoined and changed his name to %s",g_OldNames[i],NewName);
        }
      }
      else {
        if (g_OldKicked[i]==-12345678) {
          snprintf(msg,MAX_TEXT_LENGTH,"### %s who was kicked earlier has rejoined",g_OldNames[i]);
        }
        else if (g_OldKicked[i]>0) {
          snprintf(msg,MAX_TEXT_LENGTH,"### %s who was banned earlier for %i minutes has rejoined",g_OldNames[i],g_OldKicked[i]);
        }
      }
      if (msg[0]) {
        AnnounceToAdmins(msg,print_type:print_chat,0);
      }
      return;
    }
    i--;
  }
}
 
AnnounceToAdmins(msg[],print_type:location,Except) {
  new j;
  new strAdmin[MAX_NAME_LENGTH];
  new c=maxplayercount();
  for(j=1;j<c;j++) {
    if (j!=Except) {
      if (playerinfo(j,strAdmin,MAX_NAME_LENGTH)) {
        if (access(ACCESS_WHOIS,strAdmin)) {
          language_saybyname(strAdmin,msg,location,0,0,0,0);
        }
      }
    }
  }
}
 
GenerateFilename(Path[],ParsedAuthID[],&HashParsedAuthID,Base[],AuthID[],FileType[]) {
  ParseAuthID(AuthID,ParsedAuthID);
  HashParsedAuthID= Hash(ParsedAuthID);
  if (FileType[0]=='.') {
    snprintf(Path,MAX_DATA_LENGTH,"%s/%i/%s%s",Base,HashParsedAuthID,ParsedAuthID,FileType);
  }
  else {
    snprintf(Path,MAX_DATA_LENGTH,"%s/%i/%s_%s",Base,HashParsedAuthID,ParsedAuthID,FileType);
  }
}
 
ParseAuthID(AuthID[],ParsedAuthID[]) {
  new c=strlen(AuthID);
  new i;
 
  for(i=0;i<=c;i++) {
    if (
      ((AuthID[i]>='a' && AuthID[i]<='z'))
        ||
      ((AuthID[i]>='A' && AuthID[i]<='Z'))
        ||
      ((AuthID[i]>='0' && AuthID[i]<='9'))
        ||
      AuthID[i]==0
       ) {
      ParsedAuthID[i]=AuthID[i];
    }
    else {
      ParsedAuthID[i]='-';
    }
  }
}
 
UserIndexFromSessionID(nSessionID) {
  new Name[MAX_NAME_LENGTH];
  new i;
  new nTestSessionID;
  for (i=1;i<MAX_PLAYERS;i++) {
    if (playerinfo(i,Name,MAX_NAME_LENGTH,nTestSessionID)) {
      if (nSessionID == nTestSessionID) {
	    return i;
      }
    }
  }
  return 0;
}
 
RemoveCD(Name[]) {
  if (strncmp(Name,"[No C-D]",8)==0) {
    strstrip(Name,8);
  }
  else if (strncmp(Name,"[Old C-D]",9)==0) {
	strstrip(Name,9);
  }
}
 
MediateCD(UserIndex,AuthID[]) {
  /* Check if mediation is enabled and CD is running on the server */
  if ((g_RcdEnabled==0) || (g_CheatingDeath==0) || (g_CDRequiredMode == 1)) {
    return 0;
  }
 
  /* Check if player has failed CD authentication */
  if ((g_CD_Status[UserIndex] != 1) && (g_CD_Status[UserIndex] != 2)) {
    return 0;
  }
 
  /* Watched players must use CD */
  if (g_Watches[UserIndex]) {
    RequireCD(UserIndex);
    return 1;
  }
 
  /* Players with less than rcdminimum rounds played need not use CD */
  if (g_Rounds[UserIndex] <= g_RcdMinimum) {
    return 0;
  }
 
  /* Players with more than rcdmaximum rounds played need CD */
  if (g_Rounds[UserIndex] > g_RcdMaximum) {
    RequireCD(UserIndex);
    return 1;
  }
 
  /* Players with a non-cd K:D ratio higher than g_RcdMaximum need CD */
  new Path[MAX_DATA_LENGTH];
  new ParsedAuthID[MAX_AUTHID_LENGTH];
  new HashParsedAuthID;
  GenerateFilename(Path,ParsedAuthID,HashParsedAuthID,"addons/whois/db",AuthID,"Kills.xml");
  new K[4];
  new D[4];
 
  LoadKillStatus(Path,K,D);
 
  K[KD_NOCD_ADMIN]+=g_Kills[UserIndex][KD_NOCD_ADMIN];
  K[KD_NOCD_NOADMIN]+=g_Kills[UserIndex][KD_NOCD_NOADMIN];
  K[KD_CD_ADMIN]+=g_Kills[UserIndex][KD_CD_ADMIN];
  K[KD_CD_NOADMIN]+=g_Kills[UserIndex][KD_CD_NOADMIN];
  D[KD_NOCD_ADMIN]+=g_Deaths[UserIndex][KD_NOCD_ADMIN];
  D[KD_NOCD_NOADMIN]+=g_Deaths[UserIndex][KD_NOCD_NOADMIN];
  D[KD_CD_ADMIN]+=g_Deaths[UserIndex][KD_CD_ADMIN];
  D[KD_CD_NOADMIN]+=g_Deaths[UserIndex][KD_CD_NOADMIN];
 
  new KNoCD = K[KD_NOCD_ADMIN] + K[KD_NOCD_NOADMIN];
  new DNoCD = D[KD_NOCD_ADMIN] + D[KD_NOCD_NOADMIN];
 
  if (DNoCD == 0) {
    DNoCD = 1;
  }
  if (KNoCD >= DNoCD * g_RcdRatio) {
    RequireCD(UserIndex);
    return 1;
  }
 
  return 0;
}
 
RequireCD(UserIndex) {
  BigConsoleMessage(UserIndex,"You are required to use an up-to-date copy of^nCHEATING DEATH to continue to play on this server^n^nDownload it from www.unitedadmins.com");
 
  new Name[MAX_NAME_LENGTH];
  playerinfo(UserIndex,Name,MAX_NAME_LENGTH);
  set_timer("DelayedKick",2,1,Name);
}
 
BigConsoleMessage(UserIndex,Msg[]) {
  language_say(UserIndex,"^n^n^n*******************************************************",print_type:print_console,0,0,0,0);
  language_say(UserIndex,Msg,print_type:print_console,0,0,0,0);
  language_say(UserIndex,"*******************************************************^n^n",print_type:print_console,0,0,0,0);
 
}