#!/usr/bin/perl
#
# HLstatsX - Real-time player and clan rankings and statistics for Half-Life 2
# http://www.hlstatsx.com/
# Copyright (C) 2006 Tobias Oetzel (Tobi@hlstatsx.com)
#
# HLstatsX is an enhanced version of HLstats made by Simon Garner
# HLstats - Real-time player and clan rankings and statistics for Half-Life
# http://sourceforge.net/projects/hlstats/
# Copyright (C) 2001  Simon Garner
#             
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#

use strict;
no strict 'vars';

##
## Settings
##

# $opt_configfile - Absolute path and filename of configuration file.
$opt_configfile = "./hlstats.conf";

# $opt_libdir - Directory to look in for local required files
#               (our *.plib, *.pm files).
$opt_libdir = "./";


##
##
################################################################################
## No need to edit below this line
##

use Getopt::Long;
use Time::Local;
use IO::Socket;
use IO::Select;
use DBI;
use Digest::MD5;

require "$opt_libdir/ConfigReaderSimple.pm";
require "$opt_libdir/TRcon.pm";
require "$opt_libdir/HLstats_Server.pm";
require "$opt_libdir/HLstats_Player.pm";
do "$opt_libdir/HLstats.plib";
do "$opt_libdir/HLstats_EventHandlers.plib";

$|=1;
Getopt::Long::Configure ("bundling");


$last_trend_timestamp = 0;

##
## Functions
##


sub is_number ($) { ( $_[0] ^ $_[0] ) eq '0' }

#
# void printEvent (int code, string description)
#
# Logs event information to stdout.
#

sub printEvent
{
	my ($code, $description, $update_timestamp, $force_output) = @_;
	
	if ( (($g_debug > 0) && ($g_stdin == 0))|| (($g_stdin == 1) && ($force_output == 1)) )
	{
     	my ($sec,$min,$hour,$mday,$mon,$year) = localtime(time());
	    my $timestamp = sprintf("%04d-%02d-%02d %02d:%02d:%02d", $year+1900, $mon+1, $mday, $hour, $min, $sec);
        if ($update_timestamp == 0) {
          $timestamp = $ev_timestamp; 
        }  
		if (is_number($code)) {
  		  printf("%s: %21s - E%03d: %s\n", $timestamp, $s_addr, $code, $description);
  		} else {
  		  printf("%s: %21s - %s: %s\n", $timestamp, $s_addr, $code, $description);
  		}  
	}
}


#
# void printNotice (string notice)
#
# Prins a debugging notice to stdout.
#

sub printNotice
{
	my ($notice) = @_;
	
	if ($g_debug > 1)
	{
		print ">> $notice\n";
	}
}


sub track_hlstats_trend
{

    my $new_timestamp  = time();
    if ($last_trend_timestamp > 0)
    {
      if ($last_trend_timestamp+300 < $new_timestamp)
      {
    	my $result = &doQuery("SELECT count(*), game FROM hlstats_Players GROUP BY game");
        while ( my($total_players, $game) = $result->fetchrow_array)
        {
          my $data = &doQuery("SELECT SUM(kills), SUM(headshots), count(serverId), SUM(act_players), SUM(max_players) FROM hlstats_Servers WHERE game='$game'");
  	      my ($total_kills, $total_headshots, $total_servers, $act_slots, $max_slots) = $data->fetchrow_array;
  	      if ($max_slots > 0) {
            if ($act_slots > $max_slots)  {
              $act_slots = $max_slots;
            }
          }
   	      &doQuery("INSERT IGNORE INTO hlstats_Trend SET timestamp='".$new_timestamp."', game='".$game."', players='".$total_players."', ".
   	               "kills='".$total_kills."', headshots='".$total_headshots."', servers='".$total_servers."', act_slots='".$act_slots."', max_slots='".$max_slots."'");
     	}  
     	$last_trend_timestamp = $new_timestamp;
        &::printEvent("HLSTATSX", "Insert new server trend timestamp", 1);
  	  }
  	} else {
      $last_trend_timestamp = $new_timestamp;
  	}  
}

#
# void recordEvent (string table, array cols, bool getid, [mixed eventData ...])
#
# Adds an event to an Events table.
#

sub recordEvent
{
	my $table = shift;
	my $getid = shift;
	my @coldata = @_;
	
	my @cols = @{$g_eventTables{$table}};
	my $lastid = -1;
	
	my $insertType = "";
	$insertType = " DELAYED" if ($db_lowpriority);
	
	my $query = "
		INSERT$insertType INTO
			hlstats_Events_$table
			(
				eventTime,
				serverId,
				map"
	;
	
	foreach $i (@cols)
	{
		$query .= ",\n$i";
	}
	
	$query .= "
			)
		VALUES
		(
			$ev_datetime,
			'$g_servers{$s_addr}->{id}',
			'".$g_servers{$s_addr}->get_map()."'"
	;
	
	for $i (@coldata)
	{
		$query .= ",\n'" . &quoteSQL($i) . "'";
	}
	
	$query .= "
		)
	";
	
	my $result = &doQuery($query);
	
	if ($getid)
	{
		$result = &doQuery("SELECT LAST_INSERT_ID()");
		($lastid) = $result->fetchrow_array;
		return $lastid;
	}
	
	$result->finish;
}


#
# array calcSkill (int skill_mode, int killerSkill, int victimSkill, string weapon)
#
# Returns an array, where the first index contains the killer's new skill, and
# the second index contains the victim's new skill. 
#

sub calcSkill
{
	my ($skill_mode, $killerSkill, $victimSkill, $weapon) = @_;
	my @newSkill;
	
	$killerSkill = 1 if ($killerSkill < 1);
	$victimSkill = 1 if ($victimSkill < 1);
	
	if ($g_debug > 2)
	{
		&printNotice("Begin calcSkill: killerSkill=$killerSkill");
		&printNotice("Begin calcSkill: victimSkill=$victimSkill");
	}

	# Look up the weapon's skill modifier
	my $query = "
		SELECT
			modifier
		FROM
			hlstats_Weapons
		WHERE
			code='" . &quoteSQL($weapon) . "' AND
			game='$g_servers{$s_addr}->{game}'
	";
	my $result = &doQuery($query);

	if ($result->rows)
	{
		($modifier) = $result->fetchrow_array;
	}
	else
	{
		# if the weapon has no modifier specified, then we default to 1.
		$modifier = 1.00;
	}
	$result->finish;
	
	# Calculate the new skills
	my $killerSkillChange = ($victimSkill / $killerSkill) * 5 * $modifier;

    my $victimSkillChange = 0;
    if ($skill_mode == 0)  {	
   	  $victimSkillChange = ($victimSkill / $killerSkill) * 5 * $modifier;
    } elsif ($skill_mode == 1) {	 
   	  $victimSkillChange = ($victimSkill / $killerSkill) * 5 * $modifier;
   	  $victimSkillChange = $victimSkillChange * 0.75;
    } elsif ($skill_mode == 2) {	 
   	  $victimSkillChange = ($victimSkill / $killerSkill) * 5 * $modifier;
   	  $victimSkillChange = $victimSkillChange * 0.5;
    } elsif ($skill_mode == 3) {	 
      $victimSkillChange = ($victimSkill / $killerSkill) * 5 * $modifier;
      $victimSkillChange = $victimSkillChange * 0.25;
    } elsif ($skill_mode == 4) {	 
      $victimSkillChange = 0;
    }
	
	if ($killerSkillChange > $g_skill_maxchange)
	{
		&printNotice("Capping killer skill change of $killerSkillChange to $g_skill_maxchange") if ($g_debug > 2);
		$killerSkillChange = $g_skill_maxchange;
	}
	if ($victimSkillChange > $g_skill_maxchange)
	{
		&printNotice("Capping victim skill change of $victimSkillChange to $g_skill_maxchange") if ($g_debug > 2);
		$victimSkillChange = $g_skill_maxchange;
	}
	
	if ($g_skill_maxchange > $g_skill_minchange)
	{
  	  if ($killerSkillChange < $g_skill_minchange)
	  {
		&printNotice("Capping killer skill change of $killerSkillChange to $g_skill_minchange") if ($g_debug > 2);
		$killerSkillChange = $g_skill_minchange;
	  } 
	
	  if (($victimSkillChange < $g_skill_minchange) && ($skill_mode != 4))
	  {
		&printNotice("Capping victim skill change of $victimSkillChange to $g_skill_minchange") if ($g_debug > 2);
		$victimSkillChange = $g_skill_minchange;
	  }
	}
	
	
	$killerSkill += $killerSkillChange;
	$victimSkill -= $victimSkillChange;
	
	# we want int not float
	$killerSkill = sprintf("%d", $killerSkill + 0.5);
	$victimSkill = sprintf("%d", $victimSkill + 0.5);
	
	if ($g_debug > 2)
	{
		&printNotice("End calcSkill: killerSkill=$killerSkill");
		&printNotice("End calcSkill: victimSkill=$victimSkill");
	}

	return ($killerSkill, $victimSkill);
}


# Gives members of 'team' an extra 'reward' skill points. Members of the team
# who have been inactive (no events) for more than 2 minutes are not rewarded.
#

sub rewardTeam
{
	my ($team, $reward, $actionid, $actionname, $actioncode) = @_;
	
	my $player;
	
	&printNotice("Rewarding team \"$team\" with \"$reward\" skill for action \"$actionid\" ...");
	
	foreach $player (values(%g_players))
	{
  	  if (($g_servers{$s_addr}->{ignore_bots} == 1) && ($player->{is_bot} == 1))
  	  {
		$desc = "(IGNORED) BOT: ";
  	  } else {
		my $player_team      = $player->get("team");
		my $player_server    = $player->get("server");
		my $player_timestamp = $player->get("timestamp");
		
		if (($player_server eq $s_addr) && ($ev_unixtime - $player_timestamp < 120))
		{
   		    if ($player_team eq $team)
   		    {
				if ($g_debug > 2)
				{
					&printNotice("Rewarding " . $player->getInfoString() . " with \"$reward\" skill for action \"$actionid\"");
				}
			
				&recordEvent(
					"TeamBonuses", 0,
					$player->get("playerid"),
					$actionid,
					$reward
				);

   			    $player->increment("skill", $reward, 1);
				$player->increment("session_skill", $reward, 1);
				$player->updateDB();
			}
			
            if ($player->{is_bot} == 0) {
   		      if ($player_team eq $team) {
  			    $msg = $player->{plain_uniqueid}.chr(0xff).$actioncode.chr(0xff)."0".chr(0xff);
  			  } else {
  			    $msg = $player->{plain_uniqueid}.chr(0xff).$actioncode.chr(0xff)."1".chr(0xff);
  			  }  
  			  $g_servers{$s_addr}->send_global_stats("T", $msg, "TeamAction - $actioncode");
  			}  
			
			
  		 }
		 if ($player->{is_bot} == 0)  {
           my $p_userid  = $player->get("userid");
           if (($g_servers{$s_addr}->get("broadcasting_events") == 1) && ($g_servers{$s_addr}->get("broadcasting_command_steamid") == 1) && ($g_servers{$s_addr}->get("broadcasting_player_actions") == 1)) {
             if ($player->get("display_events") == 1) {
       	       if ($reward < 0) {
      	         $g_servers{$s_addr}->dorcon($g_servers{$s_addr}->get("broadcasting_command")." \"$p_userid\" $team lost ".abs($reward)." points for $actionname");
               } else	{
      	         $g_servers{$s_addr}->dorcon($g_servers{$s_addr}->get("broadcasting_command")." \"$p_userid\" $team got $reward points for $actionname");
      	       }  
      	     }  
       	   }
       	 }    
  	   }	  
	}
   if (($g_servers{$s_addr}->get("broadcasting_events") == 1) && ($g_servers{$s_addr}->get("broadcasting_command_steamid") == 0) && ($g_servers{$s_addr}->get("broadcasting_player_actions") == 1)) {
	 if ($reward<0) {
       $g_servers{$s_addr}->dorcon($g_servers{$s_addr}->get("broadcasting_command")." $team lost ".abs($reward)." points for $actionname");
	 } else	{
   	   $g_servers{$s_addr}->dorcon($g_servers{$s_addr}->get("broadcasting_command")." $team got $reward points for $actionname");
   	 }  
   }	 
}


#
# int getPlayerId (int uniqueId)
#
# Looks up a player's ID number, from their unique (WON) ID. Returns their PID.
#

sub getPlayerId
{
	my ($uniqueId) = @_;

	my $query = "
		SELECT
			playerId
		FROM
			hlstats_PlayerUniqueIds
		WHERE
			uniqueId='" . &::quoteSQL($uniqueId) . "' AND
			game='" . $g_servers{$s_addr}->{game} . "'
	";
	my $result = &doQuery($query);

	if ($result->rows)
	{
		my ($playerId) = $result->fetchrow_array;
		$result->finish;
		return $playerId;
	}
	else
	{
		$result->finish;
		return 0;
	}
}



#
# int updatePlayerProfile (object player, string field, string value)
#
# Updates a player's profile information in the database.
#

sub updatePlayerProfile
{
	my ($player, $field, $value) = @_;
	
	unless ($player)
	{
		&printNotice("updatePlayerInfo: Bad player");
		return 0;
	}

	$value = &quoteSQL($value);

	if ($value eq "none" || $value eq " ")
	{
		$value = "";
	}
	
	my $playerName = &abbreviate($player->get("name"));
	my $playerId   = $player->get("playerid");

	&doQuery("
		UPDATE
			hlstats_Players
		SET
			$field='$value'
		WHERE
			playerId='$playerId'
	");
	
	if ($g_servers{$s_addr}->get("player_events") == 1)  
	{
       my $p_userid  = $player->get("plain_userid");
       my $p_is_bot  = $player->get("is_bot");
       if (($p_is_bot == 0) && ($g_servers{$s_addr}->get("player_command_steamid") == 1)) {
	      $cmd_str = $g_servers{$s_addr}->get("player_command")." \"$p_userid\" SET command successful for '$playerName'.";
       } else {
	      $cmd_str = $g_servers{$s_addr}->get("player_command")." SET command successful for '$playerName'.";
       }
	   $g_servers{$s_addr}->dorcon($cmd_str);
	}	
	return 1;
}




#
# mixed getClanId (string name)
#
# Looks up a player's clan ID from their name. Compares the player's name to tag
# patterns in hlstats_ClanTags. Patterns look like:  [AXXXXX] (matches 1 to 6
# letters inside square braces, e.g. [ZOOM]Player)  or  =\*AAXX\*= (matches
# 2 to 4 letters between an equals sign and an asterisk, e.g.  =*RAGE*=Player).
#
# Special characters in the pattern:
#    A    matches one character  (i.e. a character is required)
#    X    matches zero or one characters  (i.e. a character is optional)
#    a    matches literal A or a
#    x    matches literal X or x
#
# If no clan exists for the tag, it will be created. Returns the clan's ID, or
# 0 if the player is not in a clan.
#

sub getClanId
{
	my ($name) = @_;
	
	my $clanTag  = "";
	my $clanName = "";
	my $clanId   = 0;
	
	my $result = &doQuery("
		SELECT
			pattern,
			position,
			LENGTH(pattern) AS pattern_length
		FROM
			hlstats_ClanTags
		ORDER BY
			pattern_length DESC,
			id
	");
	
	while ( my($pattern, $position) = $result->fetchrow_array)
	{
		my $regpattern = quotemeta($pattern);
		$regpattern =~ s/([A-Za-z0-9]+[A-Za-z0-9_-]*)/\($1\)/; # to find clan name from tag
		$regpattern =~ s/A/./g;
		$regpattern =~ s/X/.?/g;
		
		if ($g_debug > 2)
		{
			&printNotice("regpattern=$regpattern");
		}
		
		if ((($position eq "START" || $position eq "EITHER") && $name =~ /^($regpattern).+/i) ||
			(($position eq "END"   || $position eq "EITHER") && $name =~ /.+($regpattern)$/i))
		{
			if ($g_debug > 2)
			{
				&printNotice("pattern \"$regpattern\" matches \"$name\"! 1=\"$1\" 2=\"$2\"");
			}
			
			$clanTag  = $1;
			$clanName = $2;
			last;
		}
	}
	
	unless ($clanTag)
	{
		return 0;
	}

	my $query = "
		SELECT
			clanId
		FROM
			hlstats_Clans
		WHERE
			tag='" . &quoteSQL($clanTag) . "' AND
			game='$g_servers{$s_addr}->{game}'
	";
	$result = &doQuery($query);

	if ($result->rows)
	{
		($clanId) = $result->fetchrow_array;
		$result->finish;
		return $clanId;
	}
	else
	{
		# The clan doesn't exist yet, so we create it.
		$query = "
			INSERT INTO
				hlstats_Clans
				(
					tag,
					name,
					game
				)
			VALUES
			(
				'" . &quoteSQL($clanTag)  . "',
				'" . &quoteSQL($clanName) . "',
				'$g_servers{$s_addr}->{game}'
			)
		";
		$result = &doQuery($query);
		$result->finish;
		
		$result = &doQuery("SELECT LAST_INSERT_ID()");
		($clanId) = $result->fetchrow_array;

		&printNotice("Created clan \"$clanName\" <C:$clanId> with tag "
				. "\"$clanTag\" for player \"$name\"");

		return $clanId;
	}
}



#
# object getServer (string address, int port)
#
# Looks up a server's ID number in the Servers table, by searching for a
# matching IP address and port. NOTE you must specify IP addresses in the
# Servers table, NOT hostnames.
#
# Returns a new "Server object".
#

sub getServer
{
	my ($address, $port) = @_;

	my $query = "
		SELECT
			serverId,
			game,
			name,
			rcon_password,
			publicaddress
		FROM
			hlstats_Servers
		WHERE
			address='$address' AND
			port='$port'
	";
	my $result = &doQuery($query);

	if ($result->rows)
	{
		my ($serverId, $game, $name, $rcon_pass, $publicaddress) = $result->fetchrow_array;
		$result->finish;

		return new HLstats_Server($serverId, $address, $port, $name, $rcon_pass, $game, $publicaddress);
	}
	else
	{
		$result->finish;
		return 0;
	}
}


#
# boolean sameTeam (string team1, string team2)
#
# This should be expanded later to allow for team alliances (e.g. TFC-hunted).
#

sub sameTeam
{
	my ($team1, $team2) = @_;
	
	if (($team1 eq $team2) && (($team1 ne "Unassigned") || ($team2 ne "Unassigned")))
	{
		return 1;
	}
	else
	{
		return 0;
	}
}


#
# string getPlayerInfoString (object player, string ident)
#

sub getPlayerInfoString
{
	my ($player) = shift;
	my @ident = @_;
	
	if ($player)
	{
		return $player->getInfoString();
	}
	else
	{
		return "(" . join(",", @ident) . ")";
	}
}



#
# array getPlayerInfo (string player, string forced_uniqueid, string $ipAddr)
#
# Get a player's name, uid, wonid and team from "Name<uid><wonid><team>".
#

sub getPlayerInfo
{
	my ($player, $create_player, $forced_uniqueid, $ipAddr) = @_;

	if ($player =~ /^(.+)<(\d+)><([^<>]+)><([^<>]*)>$/)
	{
	
		my $name     = $1;
		my $userid   = $2;
		my $uniqueid = $3;
		my $team     = $4;
		my $bot      = 0;
		
		if (($uniqueid eq "Console") && ($team eq "Console")) {
		  return 0;
		}  
		
		if ($forced_uniqueid)
		{
			$uniqueid = $forced_uniqueid;
		}
		elsif ($g_mode eq "NameTrack")
		{
			$uniqueid = $name;
		}
		elsif ($g_mode eq "LAN")
		{
		    
		    if ($ipAddr ne "") {
              $g_lan_noplayerinfo->{"$s_addr/$userid/$name"} = {
		        		             ipaddress => $ipAddr,
						             userid => $userid,
						             name => $name,
						             server => $s_addr
 			                        };
  			  $uniqueid = $ipAddr;
  			} else {
  			  my $found = 0; 
              while ( my($index, $player) = each(%g_players) ) {	 
                if (($player->{server} eq $s_addr) &&
                    ($player->{userid} eq $userid) &&
                    ($player->{name}   eq $name))  {
                   $uniqueid = $player->{uniqueid}; 
                   $found++;
                }   
              }
              if ($found == 0) {
                while ( my($index, $player) = each(%g_lan_noplayerinfo) ) {	 
                  if (($player->{server} eq $s_addr) &&
                      ($player->{userid} eq $userid) &&
                      ($player->{name}   eq $name))  {
                     $uniqueid = $player->{ipaddress}; 
                     $found++;
                  }    
                }  
              }
              if ($found == 0) {
    			$uniqueid = "UNKNOWN";
    	      }  
  			}  
		}
		else
		{
			foreach $botid (split(/:/, $g_bot_ids))
			{
				if ($botid eq $uniqueid)
				{
					$md5 = Digest::MD5->new;
					$md5->add($name);
					$md5->add($s_addr);

					$uniqueid = "BOT:" . $md5->hexdigest;

					$forced_uniqueid = $uniqueid if ($g_mode eq "LAN");
					$unique_id = $uniqueid if ($g_mode eq "LAN");
					$bot = 1;

					last;
				}
			}
			
			if (($uniqueid =~ /UNKNOWN/) || ($uniqueid =~ /PENDING/) || ($uniqueid =~ /VALVE_ID_LAN/))
			{
				return {
					name     => $name,
					userid   => $userid,
					uniqueid => $uniqueid,
					team     => $team
				};
			}
			
		}

		if ($g_players{"$s_addr/$userid/$uniqueid"})
		{
			$haveplayer = 1;
		}
		else
		{
			$haveplayer = 0;
		}
		
		if ($haveplayer && $g_players{"$s_addr/$userid/$uniqueid"}->get("uniqueid") eq $uniqueid)
		{
			my $player = $g_players{"$s_addr/$userid/$uniqueid"};
			if ($player) {
			  if ($player->get("team") eq "") {
			    $player->set("team", $team);
  			    $player->updateDB();
  			  }
  			  $player->updateTimestamp();
			}  
		}
		else
		{
			if ($g_mode ne "LAN" || $forced_uniqueid)
			{
			   if ((!$haveplayer) && ($create_player > 0))  {
  				   # Add the player to our hash of player objects
				   $g_players{"$s_addr/$userid/$uniqueid"} = new HLstats_Player(
					  server => $s_addr,
					  server_id => $g_servers{$s_addr}->{id},
					  userid => $userid,
					  uniqueid => $uniqueid,
					  name => $name,
					  team => $team,
					  is_bot => $bot
				   );
				   
				   if ($g_preconnect->{"$s_addr/$userid/$name"})
				   {
	   			      &printEvent("SERVER", "LATE CONNECT [$name/$userid] - steam userid validated");
  					  &doEvent_Connect($userid, $uniqueid, $g_preconnect->{"$s_addr/$userid/$name"}->{"ipaddress"});
					  delete($g_preconnect->{"$s_addr/$userid/$name"});
	 			   }  
				   
				   # Increment number of players on server
                   $g_servers{$s_addr}->{numplayers}++;
                   $g_servers{$s_addr}->updateDB();
				}  
			}
			elsif (($g_mode eq "LAN") && (defined($g_lan_noplayerinfo{"$s_addr/$userid/$name"})))
			{
			   if ((!$haveplayer) && ($uniqueid ne "UNKNOWN") && ($create_player > 0))  {
 				 $g_players{"$s_addr/$userid/$uniqueid"} = new HLstats_Player(
					server => $s_addr,
   	  	    	    server_id => $g_servers{$s_addr}->{id},
					userid => $userid,
					uniqueid => $uniqueid,
					name => $name,
					team => $team,
  				    is_bot => $bot
				 );
				 delete($g_lan_noplayerinfo{"$s_addr/$userid/$name"});
  			     # Increment number of players on server
                 $g_servers{$s_addr}->{numplayers}++;
                 $g_servers{$s_addr}->updateDB();
			  } 
			}
			else
 			{
				&printNotice("No player object available for player \"$name\" <U:$userid>");
			}
		}
		
		return {
			name     => $name,
			userid   => $userid,
			uniqueid => $uniqueid,
			team     => $team,
			is_bot   => $bot
		};
	} 
	elsif ($player =~ /^(.+)<([^<>]+)>$/)
	{
		my $name     = $1;
		my $uniqueid = $2;
		my $bot      = 0;

  	    foreach $botid (split(/:/, $g_bot_ids))
  	    {
		  if ($botid eq $uniqueid)
		  {
		    $md5 = Digest::MD5->new;
			$md5->add(time());
    		$md5->add($s_addr);
			$uniqueid = "BOT:" . $md5->hexdigest;
			$bot = 1;
			last;
		  }
  	    }
		return {
			name     => $name,
			uniqueid => $uniqueid,
			is_bot   => $bot
		};
	  
	}
	elsif ($player =~ /^<><([^<>]+)><>$/)
	{
  	  my $uniqueid = $1;
      my $bot      = 0;
	  foreach $botid (split(/:/, $g_bot_ids))
  	  {
		if ($botid eq $uniqueid)
		{
		    $md5 = Digest::MD5->new;
			$md5->add(time());
    		$md5->add($s_addr);
			$uniqueid = "BOT:" . $md5->hexdigest;
			$bot = 1;
			last;
		}
  	  }

	  return {
		  uniqueid => $uniqueid,
		  is_bot   => $bot
	  };
	}  
	else
	{
		return 0;
	}
}


#
# hash getProperties (string propstring)
#
# Parse (key "value") properties into a hash.
#

sub getProperties
{
	my ($propstring) = @_;
	my %properties;
	
	while ($propstring =~ s/^\s*\((\S+)(?: "([^"]+)")?\)//)
	{
		if (defined($2))
		{
			$properties{$1} = $2;
		}
		else
		{
			$properties{$1} = 1; # boolean property
		}
	}
	
	return %properties;
}


# 
# boolean like (string subject, string compare)
#
# Returns true if 'subject' equals 'compare' with optional whitespace.
#

sub like
{
	my ($subject, $compare) = @_;
	
	if ($subject =~ /^\s*\Q$compare\E\s*$/)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}





##
## MAIN
##

# Options

$opt_help = 0;
$opt_version = 0;

$db_host = "localhost";
$db_user = "";
$db_pass = "";
$db_name = "hlstats";
$db_lowpriority = 1;

$s_ip = "";
$s_port = "27500";

$g_mailto = "";
$g_mailpath = "/bin/mail";
$g_mode = "Normal";
$g_deletedays = 5;
$g_minactivity = 28;
$g_requiremap = 0;
$g_debug = 1;
$g_nodebug = 0;
$g_rcon = 1;
$g_rcon_ignoreself = 0;
$g_rcon_record = 1;
$g_stdin = 0;
$g_server_ip = "";
$g_server_port = 27015;
$g_timestamp = 0;
$g_dns_resolveip = 1;
$g_dns_timeout = 5;
$g_skill_maxchange = 100;
$g_skill_minchange = 2;
$g_bot_ids = "BOT:0";
$g_onlyconfig_servers = 1;
$g_track_stats_trend = 0;
%g_lan_noplayerinfo = ();
%g_preconnect = ();
$g_global_banning = 0;
$g_masterserver_address = "master.hlstatsx.com";
$g_masterserver_port    = "27501";
$g_statsserver_address  = "stats.hlstatsx.com";
$g_statsserver_port     = "27502";
$g_log_chat = 0;
$g_log_chat_admins = 0;
$g_global_chat = 0;

# Usage message

$usage = <<EOT
Usage: hlstats.pl [OPTION]...
Collect statistics from one or more Half-Life2 servers for insertion into
a MySQL database.

  -h, --help                      display this help and exit
  -v, --version                   output version information and exit
  -d, --debug                     enable debugging output (-dd for more)
  -n, --nodebug                   disables above; reduces debug level
  -m, --mode=MODE                 player tracking mode (Normal, LAN or NameTrack)  [$g_mode]
      --db-host=HOST              database ip or ip:port  [$db_host]
      --db-name=DATABASE          database name  [$db_name]
      --db-password=PASSWORD      database password (WARNING: specifying the
                                    password on the command line is insecure.
                                    Use the configuration file instead.)
      --db-username=USERNAME      database username
      --dns-resolveip             resolve player IP addresses to hostnames
                                    (requires working DNS)
      --nodns-resolveip           disables above
      --dns-timeout=SEC           timeout DNS queries after SEC seconds  [$g_dns_timeout]
  -i, --ip=IP                     set IP address to listen on for UDP log data
  -p, --port=PORT                 set port to listen on for UDP log data  [$s_port]
  -r, --rcon                      enables rcon command exec support (the default)
      --norcon                    disables rcon command exec support
  -s, --stdin                     read log data from standard input, instead of
                                    from UDP socket. Must specify --server-ip
                                    and --server-port to indicate the generator
                                    of the inputted log data
      --nostdin                   disables above
      --server-ip                 specify data source IP address for --stdin
      --server-port               specify data source port for --stdin  [$g_server_port]
  -t, --timestamp                 tells HLstatsX to use the timestamp in the log
                                    data, instead of the current time on the
                                    database server, when recording events
      --notimestamp               disables above
      --masterserveraddr          Set the masterserver from HLstatsX [master.hlstatsx.com]
      --masterserverport          Set the masterserver port [$g_masterserver_port]
      --statsserveraddr           Set the global statsserver from HLstatsX [stats.hlstatsx.com]
      --statsserverport           Set the global statsserver port [$g_statsserver_port]

Long options can be abbreviated, where such abbreviation is not ambiguous.
Default values for options are indicated in square brackets [...].

Most options can be specified in the configuration file:
  $opt_configfile
Note: Options set on the command line take precedence over options set in the
configuration file. The configuration file name is set at the top of hlstats.pl.

HLstatsX: http://www.hlstatsx.com
EOT
;

%g_config_servers = ();

# Read Config File

if ($opt_configfile && -r $opt_configfile)
{
	$conf = ConfigReaderSimple->new($opt_configfile);
	$conf->parse();
	
	%directives = (
		"DBHost",		          "db_host",
		"DBUsername",	          "db_user",
		"DBPassword",	          "db_pass",
		"DBName",		          "db_name",
		"DBLowPriority",          "db_lowpriority",
		"BindIP",		          "s_ip",
		"Port",			          "s_port",
		"MailTo",		          "g_mailto",
		"MailPath",		          "g_mailpath",
		"Mode",			          "g_mode",
		"DeleteDays",	          "g_deletedays",
		"MinActivity",            "g_minactivity",
		"DebugLevel",	          "g_debug",
		"UseTimestamp",	          "g_timestamp",
		"DNSResolveIP",	          "g_dns_resolveip",
		"DNSTimeout",	          "g_dns_timeout",
		"RconIgnoreSelf",         "g_rcon_ignoreself",
		"Rcon",			 	      "g_rcon",
		"RconRecord",	          "g_rcon_record",
		"MinPlayers",             "g_minplayers",
		"SkillMaxChange",         "g_skill_maxchange",
		"SkillMinChange",         "g_skill_minchange",
		"AllowOnlyConfigServers", "g_onlyconfig_servers",
		"TrackStatsTrend",        "g_track_stats_trend",
		"GlobalBanning",          "g_global_banning",
        "LogChat",                "g_log_chat",
        "LogChatAdmins",          "g_log_chat_admins",
        "GlobalChat",             "g_global_chat",
		"Servers",                "g_config_servers"	
	);
	&doConf($conf, %directives);
	
	
	# setting defaults
	while (my($addr, $server) = each(%g_config_servers)) {
	
      if (!defined($g_config_servers{$addr}{"MinPlayers"})) {
        $g_config_servers{$addr}{"MinPlayers"}                    = 6; 
      }  
      if (!defined($g_config_servers{$addr}{"RawSocketSupport"})) {
        $g_config_servers{$addr}{"RawSocketSupport"}              = 0; 
      }  
      if (!defined($g_config_servers{$addr}{"RawSocketHelpNotice"})) {
        $g_config_servers{$addr}{"RawSocketHelpNotice"}           = 0; 
      }  

      if (!defined($g_config_servers{$addr}{"DisplayResultsInBrowser"})) {
        $g_config_servers{$addr}{"DisplayResultsInBrowser"}       = 0; 
      }  

      if (!defined($g_config_servers{$addr}{"MasterServerData"})) {
        $g_config_servers{$addr}{"MasterServerData"}               = 7;
      } elsif ($g_config_servers{$addr}{"MasterServerData"} == 2) {
        $g_config_servers{$addr}{"MasterServerData"}               = 7;
      }
      if (!defined($g_config_servers{$addr}{"MasterServerInterval"})) {
        $g_config_servers{$addr}{"MasterServerInterval"}           = 200;
      } elsif (($g_config_servers{$addr}{"MasterServerInterval"} < 50) || ($g_config_servers{$addr}{"MasterServerInterval"} > 1000)) {
        $g_config_servers{$addr}{"MasterServerInterval"}           = 200;
      }
      if (!defined($g_config_servers{$addr}{"BroadCastEvents"})) {
        $g_config_servers{$addr}{"BroadCastEvents"}                = 0;
      }
      if (!defined($g_config_servers{$addr}{"BroadCastPlayerActions"})) {
        $g_config_servers{$addr}{"BroadCastPlayerActions"}         = 0;
      }
      if (!defined($g_config_servers{$addr}{"BroadCastEventsCommand"})) {
        $g_config_servers{$addr}{"BroadCastEventsCommand"}         = "say";
      }
      if (!defined($g_config_servers{$addr}{"BroadCastEventsCommandSteamid"})) {
        $g_config_servers{$addr}{"BroadCastEventsCommandSteamid"}  = 0;
      }
      if (!defined($g_config_servers{$addr}{"BroadCastEventsCommandAnnounce"})) {
        $g_config_servers{$addr}{"BroadCastEventsCommandAnnounce"} = "say";
      }
      if (!defined($g_config_servers{$addr}{"PlayerEvents"})) {
        $g_config_servers{$addr}{"PlayerEvents"}                   = 1;
      }
      if (!defined($g_config_servers{$addr}{"PlayerEventsCommand"})) {
        $g_config_servers{$addr}{"PlayerEventsCommand"}            = "say";
      }
      if (!defined($g_config_servers{$addr}{"PlayerEventsCommandSteamid"})) {
        $g_config_servers{$addr}{"PlayerEventsCommandSteamid"}     = 0;
      }
      if (!defined($g_config_servers{$addr}{"PlayerEventsCommandOSD"})) {
        $g_config_servers{$addr}{"PlayerEventsCommandOSD"}         = "";
      }
      if (!defined($g_config_servers{$addr}{"PlayerEventsAdminCommand"})) {
        $g_config_servers{$addr}{"PlayerEventsAdminCommand"}       = "";
      }
      if (!defined($g_config_servers{$addr}{"ShowStats"})) {
        $g_config_servers{$addr}{"ShowStats"}                      = 1;
      }
      if (!defined($g_config_servers{$addr}{"AutoTeamBalance"})) {
        $g_config_servers{$addr}{"AutoTeamBalance"}                = 0;
      }
      if (!defined($g_config_servers{$addr}{"AutoBanRetry"})) {
        $g_config_servers{$addr}{"AutoBanRetry"}                   = 0;
      }

      if (!defined($g_config_servers{$addr}{"TrackServerLoad"})) {
        $g_config_servers{$addr}{"TrackServerLoad"}                = 0;
      }
      if (!defined($g_config_servers{$addr}{"MinimumPlayersRank"})) {
        $g_config_servers{$addr}{"MinimumPlayersRank"}             = 0;
      }
      if (!defined($g_config_servers{$addr}{"Admins"})) {
        $g_config_servers{$addr}{"Admins"}                         = "";
      }
      if (!defined($g_config_servers{$addr}{"SwitchAdmins"})) {
        $g_config_servers{$addr}{"SwitchAdmins"}                   = 0;
      }
      if (!defined($g_config_servers{$addr}{"IgnoreBots"})) {
        $g_config_servers{$addr}{"IgnoreBots"}                     = 0;
      }
      if (!defined($g_config_servers{$addr}{"SkillMode"})) {
        $g_config_servers{$addr}{"SkillMode"}                      = 0;
      }
      if (!defined($g_config_servers{$addr}{"GameType"})) {
        $g_config_servers{$addr}{"GameType"}                       = 0;
      }
      if (!defined($g_config_servers{$addr}{"Mod"})) {
        $g_config_servers{$addr}{"Mod"}                            = "";
      }


    }
}
else
{
	print "-- Warning: unable to open configuration file '$opt_configfile'\n";
}

# Read Command Line Arguments

GetOptions(
	"help|h"			=> \$opt_help,
	"version|v"			=> \$opt_version,
	"debug|d+"			=> \$g_debug,
	"nodebug|n+"		=> \$g_nodebug,
	"mode|m=s"			=> \$g_mode,
	"db-host=s"			=> \$db_host,
	"db-name=s"			=> \$db_name,
	"db-password=s"		=> \$db_pass,
	"db-username=s"		=> \$db_user,
	"dns-resolveip!"	=> \$g_dns_resolveip,
	"dns-timeout=i"		=> \$g_dns_timeout,
	"ip|i=s"			=> \$s_ip,
	"port|p=i"			=> \$s_port,
	"rcon!"				=> \$g_rcon,
	"r"					=> \$g_rcon,
	"stdin!"			=> \$g_stdin,
	"s"					=> \$g_stdin,
	"server-ip=s"		=> \$g_server_ip,
	"server-port=i"		=> \$g_server_port,
	"timestamp!"		=> \$g_timestamp,
	"t"					=> \$g_timestamp,
	"masterserveraddr"  => \$g_masterserver_address,
	"masterserverport"  => \$g_masterserver_port,
	"statsserveraddr"   => \$g_statsserver_address,
	"statsserverport"   => \$g_statsserver_port
) or die($usage);

if ($opt_help)
{
	print $usage;
	exit(0);
}

if ($opt_version)
{
	print "hlstats.pl (HLstatsX) V$g_version\n"
		. "Real-time player and clan rankings and statistics for Half-Life:Source\n"
		. "Copyright (C) 2005  Tobias Oetzel (Tobi@hlstatsx.com)\n"
		. "Original (C) 2001 by Simon Garner \n\n";
	
	print "Using ConfigReaderSimple module version $ConfigReaderSimple::VERSION\n";
	if ($g_rcon)
	{
		print "Using TRcon module\n";
	}
	
	print "\nThis is free software; see the source for copying conditions.  There is NO\n"
		. "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n";
	
	exit(0);
}

if ($g_mode ne "Normal" && $g_mode ne "LAN" && $g_mode ne "NameTrack")
{
	$g_mode = "Normal";
}

$g_debug -= $g_nodebug;
$g_debug = 0 if ($g_debug < 0);



# Init Timestamp
my ($sec,$min,$hour,$mday,$mon,$year) = localtime(time());
$ev_timestamp = sprintf("%04d-%02d-%02d %02d:%02d:%02d", $year+1900, $mon+1, $mday, $hour, $min, $sec);
$ev_datetime  = "NOW()";
$ev_unixtime  = time();


# Startup

&printEvent("HLSTATSX", "HLstatsX V$g_version starting...", 1);

# Create the UDP & TCP socket

if ($g_stdin)
{
	&printEvent("UDP", "UDP listen socket disabled, reading log data from STDIN.", 1);
	
	if (!$g_server_ip || !$g_server_port)
	{
		&printEvent("UDP", "ERROR: You must specify source of STDIN data using --server-ip and --server-port", 1);
		&printEvent("UDP", "Example: ./hlstats.pl --stdin --server-ip 12.34.56.78 --server-port 27015", 1);
		exit(255);
	}
	else
	{
		&printEvent("UDP", "All data from STDIN will be allocated to server '$g_server_ip:$g_server_port'.", 1);
		$s_peerhost = $g_server_ip;
		$s_peerport = $g_server_port;
	}
}
else
{
	if ($s_ip) { $ip = $s_ip . ":"; } else { $ip = "port "; }
	
	$s_socket = IO::Socket::INET->new(
		Proto=>"udp",
		LocalAddr=>"$s_ip",
		LocalPort=>"$s_port"
	) or die ("\nCan't setup UDP socket on $ip$s_port: $!\n");

	&printEvent("UDP", "Opening UDP listen socket on $ip$s_port ... ok", 1);

}

# Connect to the database

$db_conn = DBI->connect(
	"DBI:mysql:$db_name:$db_host",
	$db_user, $db_pass
) or die ("\nCan't connect to MySQL database '$db_name' on '$db_host'\n" .
	"Server error: $DBI::errstr\n");

&printEvent("MYSQL", "Connecting to MySQL database '$db_name' on '$db_host' as user '$db_user' ... connected ok", 1);

if ($g_masterserver_address !~ /[0-9]+[.][0-9]+[.][0-9]+[.][0-9]+/) {
  my $host_data = (gethostbyname($g_masterserver_address))[4];
  $g_masterserver_address = join(".", unpack("C4", $host_data));
}
&printEvent("MASTER", "Masterserver address is ".$g_masterserver_address, 1);

if ($g_statsserver_address !~ /[0-9]+[.][0-9]+[.][0-9]+[.][0-9]+/) {
  my $host_data = (gethostbyname($g_statsserver_address))[4];
  $g_statsserver_address = join(".", unpack("C4", $host_data));
}
&printEvent("GLOBALSTATS", "Global statsserver address is ".$g_statsserver_address, 1);


if ($g_track_stats_trend > 0)  {
  &printEvent("HLSTATSX", "Tracking Trend of the stats are enabled", 1);
}

if ($g_global_banning > 0)  {
  &printEvent("HLSTATSX", "Global Banning on all servers is enabled", 1);
}

&printEvent("HLSTATSX", "Maximum Skill Change on all servers are ".$g_skill_maxchange." points", 1);
&printEvent("HLSTATSX", "Minimum Skill Change on all servers are ".$g_skill_minchange." points", 1);

if ($g_log_chat > 0)  {
  &printEvent("HLSTATSX", "Players chat logging is enabled", 1);
  if ($g_log_chat_admins > 0)  {
    &printEvent("HLSTATSX", "Admins chat logging is enabled", 1);
  }
}

if ($g_global_chat == 1)  {
  &printEvent("HLSTATSX", "Broadcasting public chat to all players is enabled", 1);
} elsif ($g_gloabl_chat == 2)  {
  &printEvent("HLSTATSX", "Broadcasting public chat to admins is enabled", 1);
} else {
  &printEvent("HLSTATSX", "Broadcasting public chat is disabled", 1);
}


%g_servers = ();
%g_players = ();

%g_eventTables = (
	"TeamBonuses",
		["playerId", "actionId", "bonus"],
	"ChangeRole",
		["playerId", "role"],
	"ChangeName",
		["playerId", "oldName", "newName"],
	"ChangeTeam",
		["playerId", "team"],
	"Connects",
		["playerId", "ipAddress", "hostname", "hostgroup"],
	"Disconnects",
		["playerId"],
	"Entries",
		["playerId"],
	"Frags",
		["killerId", "victimId", "weapon", "headshot"],
	"PlayerActions",
		["playerId", "actionId", "bonus"],
	"PlayerPlayerActions",
		["playerId", "victimId", "actionId", "bonus"],
	"Suicides",
		["playerId", "weapon"],
	"Teamkills",
		["killerId", "victimId", "weapon"],
	"Rcon",
		["type", "remoteIp", "password", "command"],
	"Admin",
		["type", "message", "playerName"],
	"Statsme",
		["playerId", "weapon", "shots", "hits", "headshots", "damage", "kills", "deaths"],
	"Statsme2",
		["playerId", "weapon", "head", "chest", "stomach", "leftarm", "rightarm", "leftleg", "rightleg"],
	"StatsmeLatency",
		["playerId", "ping"],
	"StatsmeTime",
		["playerId", "time"],
	"Latency",
		["playerId", "ping"],
    "Chat",
        ["playerId", "message_mode", "message"]
);

# Finding all tables for auto optimisation
$result = &doQuery("SHOW TABLES");
while ( ($row) = $result->fetchrow_array )
{
	push(@g_allTables, $row);
}
$result->finish;
&printEvent("HLSTATSX", "HLstatsX is now running ($g_mode mode, debug level $g_debug)", 1);


# unix timestamp
$g_minactivity = $g_minactivity * 86400;

$start_time    = time();
if ($g_stdin)	{
  $g_timestamp       = 1;
  $start_parse_time  = time();
  $import_logs_count = 0;
  &printEvent("IMPORT", "Start importing logs. Every dot signs 500 parsed lines", 1, 1);
}

# Main data loop
$c = 0;

sub getLine
{
	if ($g_stdin)
	{
		return <STDIN>;
	}
	else
	{
		return 1;
	}
}

&doQuery("DELETE FROM hlstats_Livestats");
$timeout    = 0;
while ($loop = &getLine()) {

    my ($sec,$min,$hour,$mday,$mon,$year) = localtime(time());
    $ev_timestamp = sprintf("%04d-%02d-%02d %02d:%02d:%02d", $year+1900, $mon+1, $mday, $hour, $min, $sec);
    $ev_datetime  = "NOW()";
	$ev_unixtime  = time();

	if ($g_stdin) {
		$s_output = $loop;
		if (($import_logs_count > 0) && ($import_logs_count % 500 == 0))  {
		  $parse_time = time() - $start_parse_time;
		  if ($parse_time == 0) {
		    $parse_time++;
		  }  
		  print ". [".($parse_time)." sec (".sprintf("%.3f", (500 / $parse_time)).") (".%g_players.")]\n";
		  $start_parse_time = time();
		}
	} else	{ 
       if(IO::Select->new($s_socket)->can_read(2)) {  # 2 second timeout
          $s_socket->recv($s_output, 1024);
          $timeout = 0;
       } else {
          $timeout++;
          if ($timeout % 60 == 0) {
     	    &printEvent("HLSTATSX", "No data since 120 seconds");
     	  }    
       }
       
       $s_peerhost  = $s_socket->peerhost;
	   $s_peerport  = $s_socket->peerport;
	}

	if ($timeout == 0)
	{

		$s_addr = "$s_peerhost:$s_peerport";
		
		
		# country paket
	    if ( (length($s_output) > 10) && (substr($s_output, 0, 1) eq chr(0xff)) && (substr($s_output, 1, 1) eq chr(0xff)) && (substr($s_output, 2, 1) eq "A") )  {
	      $s_output = substr($s_output, 2, length($s_output)-2);

          my $q_version = "1.01";
          my @data = split chr(0xff), $s_output;
	      $cmd = $data[0];
 	      if (($cmd eq "A") && ($s_peerhost eq $g_masterserver_address)) {
            my $send_q_version = $data[1];
	        if ($q_version eq $send_q_version) {
      	      my @location = split chr(0xfe), $data[2];
      	      if (@location == 7) {
	            my $found = 0;
                while (( my($pl, $player) = each(%g_players) ) && ($found == 0)) {
                  if ($location[0] == $player->{playerid}) {
                    $player->{city}     = $location[1];
                    $player->{state}    = $location[2];
                    $player->{lat}      = $location[3];
                    $player->{lng}      = $location[4];
                    $player->{country}  = $location[5];
                    $player->{flag}     = $location[6];
                    $found++;
                  }  
	            }
          	   	&::printEvent("MASTER", "Incoming ".(length($s_output)+2)." bytes from master server at '$s_addr' [Country]", 1);
	          }  
	        }
	      }  
	      next;
        }  
        
        if ($s_peerhost eq $g_masterserver_address) {
          next;
        }
	
		$s_output =~ s/[\r\n\0]//g;	# remove naughty characters
		$s_output =~ s/\[No.C-D\]//g;	# remove [No C-D] tag
		$s_output =~ s/\[OLD.C-D\]//g;	# remove [OLD C-D] tag
		$s_output =~ s/\[NOCL\]//g;	# remove [NOCL] tag
		$s_output =~ s/\([12]\)//g;	# strip (1) and (2) from player names

		# Get the server info, if we know the server, otherwise ignore the data
		if (!$g_servers{$s_addr})
		{
		
	     	if (($g_onlyconfig_servers == 1) && (!$g_config_servers{$s_addr})) {
		 	  &printEvent(997, "NOT ALLOWED SERVER: " . $s_output);
			  next;
			} elsif (!$g_config_servers{$s_addr}) { # create std cfg.
			  my %std_cfg;
			  $std_cfg{"AddressPort"}                    = $s_addr;
			  $std_cfg{"MinPlayers"}                     = 6;
			  $std_cfg{"AdminContact"}                   = "";
			  $std_cfg{"HLStatsURL"}                     = "";
			  $std_cfg{"RawSocketSupport"}               = 0;
			  $std_cfg{"RawSocketHelpNotice"}            = 0;
			  $std_cfg{"DisplayResultsInBrowser"}        = 0;
			  $std_cfg{"MasterServerData"}               = 3;
			  $std_cfg{"MasterServerInterval"}           = 200;
			  $std_cfg{"BroadCastEvents"}                = 0;
			  $std_cfg{"BroadCastPlayerActions"}         = 0;
			  $std_cfg{"BroadCastEventsCommand"}         = "say";
			  $std_cfg{"BroadCastEventsCommandSteamid"}  = 0;
              $std_cfg{"BroadCastEventsCommandAnnounce"} = "say",
			  $std_cfg{"PlayerEvents"}                   = 1;
			  $std_cfg{"PlayerEventsCommand"}            = "say";
			  $std_cfg{"PlayerEventsCommandSteamid"}     = 0;
              $std_cfg{"PlayerEventsCommandOSD"}         = "",
			  $std_cfg{"PlayerEventsAdminCommand"}       = "";
			  $std_cfg{"ShowStats"}                      = 1;
			  $std_cfg{"TKPenalty"}                      = 50;
			  $std_cfg{"SuicidePenalty"}                 = 5;
			  $std_cfg{"AutoTeamBalance"}                = 0;
			  $std_cfg{"AutobanRetry"}                   = 0;
			  $std_cfg{"TrackServerLoad"}                = 0;
              $std_cfg{"MinimumPlayersRank"}             = 0;
              $std_cfg{"Admins"}                         = "";
              $std_cfg{"SwitchAdmins"}                   = 0;
              $std_cfg{"IgnoreBots"}                     = 0;
              $std_cfg{"SkillMode"}                      = 0;
              $std_cfg{"GameType"}                       = 0;
              $std_cfg{"Mod"}                            = "";
			  %{$g_config_servers{$s_addr}}              = %std_cfg;
		 	  &printEvent("CFG", "Created default config for unknown server [$s_addr]");
			}
			
			if ($g_config_servers{$s_addr}) {
	    	  $g_servers{$s_addr} = &getServer($s_peerhost, $s_peerport);
    	  	  if (!$g_servers{$s_addr}) {
 	    		&printEvent(997, "UNRECOGNIZED SERVER: " . $s_output);
		    	next;
   	     	  }	
    	  
		      my %s_cfg = %{$g_config_servers{$s_addr}};
        	  $g_servers{$s_addr}->set("minplayers", $s_cfg{"MinPlayers"});
        	  $g_servers{$s_addr}->set("contact", $s_cfg{"AdminContact"});
	          $g_servers{$s_addr}->set("hlstats_url", $s_cfg{"HLStatsURL"});
          
    	      if ($s_cfg{"RawSocketSupport"} == 1)  {  
       		    $g_servers{$s_addr}->set("rawsocket_support",  1);
		    	&printEvent("SERVER", "Raw-Socket support is enabled", 1); 
			  } else { 
    	 	    $g_servers{$s_addr}->set("rawsocket_support",  0);
			    &printEvent("SERVER", "Raw-Socket support is disabled", 1); 
			  }
          
	          if ($s_cfg{"RawSocketHelpNotice"} == 1)  {  
    	   	    $g_servers{$s_addr}->set("rawsocket_helpnotice",  1);
			    &printEvent("SERVER", "Help notice on Raw-Socket commands is enabled", 1); 
			  } else { 
    	 	    $g_servers{$s_addr}->set("rawsocket_helpnotice",  0);
			    &printEvent("SERVER", "Help notice on Raw-Socket commands is disabled", 1); 
			  }

	          if ($s_cfg{"DisplayResultsInBrowser"} > 0)  {  
    	   	    $g_servers{$s_addr}->set("use_browser",  1);
			    &printEvent("SERVER", "Query results will displayed in valve browser", 1); 
			  } else { 
    	 	    $g_servers{$s_addr}->set("use_browser",  0);
			    &printEvent("SERVER", "Query results will not displayed in valve browser", 1); 
			  }

    	      if ($s_cfg{"MasterServerData"} > 0)  {  
    	      
     		    $g_servers{$s_addr}->set("broadcasting_serverdata",      $s_cfg{"MasterServerData"});
	     	    $g_servers{$s_addr}->set("broadcasting_server_address",  $g_masterserver_address);
    	 	    $g_servers{$s_addr}->set("broadcasting_server_port",     $g_masterserver_port);
     		    $g_servers{$s_addr}->set("broadcasting_server_interval", $s_cfg{"MasterServerInterval"});
	     	    $g_servers{$s_addr}->set("globalstats_server_address",   $g_statsserver_address);
    	 	    $g_servers{$s_addr}->set("globalstats_server_port",      $g_statsserver_port);

		    	if ($s_cfg{"MasterServerData"} > 1) {
	  		      &printEvent("SERVER", "Extended broadcasting to master server at ".$g_masterserver_address.":".$g_masterserver_port." is enabled", 1); 
    	  	    } else  {
  			      &printEvent("SERVER", "Broadcasting to master server at ".$g_masterserver_address.":".$g_masterserver_port." is enabled", 1); 
	    	    }
		    	if ($s_cfg{"MasterServerData"} == 7) {
	  		      &printEvent("SERVER", "Broadcasting player events into the global ranking is enabled", 1); 
		    	}
 			  } else { 
	     	    $g_servers{$s_addr}->set("broadcasting_serverdata",  0);
			    &printEvent("SERVER", "Broadcasting serverdata to master server is disabled", 1); 
			  }

	          if ($s_cfg{"ShowStats"} == 1) {
    	        $g_servers{$s_addr}->set("show_stats",  1);
        	    &printEvent("SERVER", "Showing stats is enabled", 1); 
	          } else {
			    $g_servers{$s_addr}->set("show_stats",  0);
        	    &printEvent("SERVER", "Showing stats is disabled", 1); 
	          }
    	      if ($s_cfg{"BroadCastEvents"} == 1) {  
        	    $g_servers{$s_addr}->set("broadcasting_events",  1);
        	    $g_servers{$s_addr}->set("broadcasting_player_actions",  $s_cfg{"BroadCastPlayerActions"});
			    if ($s_cfg{"BroadCastEventsCommandSteamid"} == 1) {
			      $g_servers{$s_addr}->set("broadcasting_command_steamid",  $s_cfg{"BroadCastEventsCommandSteamid"});
			    }
            	$g_servers{$s_addr}->set("broadcasting_command", $s_cfg{"BroadCastEventsCommand"});
            	$g_servers{$s_addr}->set("broadcasting_command_announce", $s_cfg{"BroadCastEventsCommandAnnounce"});

	            &printEvent("SERVER", "Broadcasting Live-Events with \"".$s_cfg{"BroadCastEventsCommand"}."\" is enabled", 1); 
	            if ($s_cfg{"BroadCastEventsCommandAnnounce"} ne "") {
  	              &printEvent("SERVER", "Broadcasting Announcements with \"".$s_cfg{"BroadCastEventsCommandAnnounce"}."\" is enabled", 1); 
  	            }  
    	      } else {
			    $g_servers{$s_addr}->set("broadcasting_events",               0);
            	&printEvent("SERVER", "Broadcasting Live-Events is disabled", 1); 
	          }
    	      if ($s_cfg{"PlayerEvents"} == 1) {  
        	    $g_servers{$s_addr}->set("player_events",  1);
		    	$g_servers{$s_addr}->set("player_command", $s_cfg{"PlayerEventsCommand"});
			    if ($s_cfg{"PlayerEventsCommandSteamid"} == 1) {
			      $g_servers{$s_addr}->set("player_command_steamid", $s_cfg{"PlayerEventsCommandSteamid"});
			    }
		    	$g_servers{$s_addr}->set("player_command_osd", $s_cfg{"PlayerEventsCommandOSD"});
			    $g_servers{$s_addr}->set("player_admin_command", $s_cfg{"PlayerEventsAdminCommand"});
    	        &printEvent("SERVER", "Player Event-Handler with \"".$s_cfg{"PlayerEventsCommand"}."\" is enabled", 1); 
	            if ($s_cfg{"PlayerEventsCommandOSD"} ne "") {
  	              &printEvent("SERVER", "Displaying amx style menu with \"".$s_cfg{"PlayerEventsCommandOSD"}."\" is enabled", 1); 
  	            }  
        	  } else {
			    $g_servers{$s_addr}->set("player_events",                 0);
    	        &printEvent("SERVER", "Player Event-Handler is disabled", 1); 
        	  }
			  if ($s_cfg{"TrackServerLoad"} > 0) {
			    $g_servers{$s_addr}->set("track_server_load", "1");
  		    	&printEvent("SERVER", "Tracking server load is enabled", 1);
			  } else {
			    $g_servers{$s_addr}->set("track_server_load", "0");
  			    &printEvent("SERVER", "Tracking server load is disabled", 1);
			  }

              if ($s_cfg{"TKPenalty"} > 0) {
		        $g_servers{$s_addr}->set("tk_penalty", $s_cfg{"TKPenalty"});
		        &printEvent("SERVER", "Penalty team kills with ".$s_cfg{"TKPenalty"}." points", 1);
		      }  
              if ($s_cfg{"SuicidePenalty"} > 0) {
		        $g_servers{$s_addr}->set("suicide_penalty", $s_cfg{"SuicidePenalty"});
		        &printEvent("SERVER", "Penalty suicides with ".$s_cfg{"SuicidePenalty"}." points", 1);
		      }  

			  if ($s_cfg{"AutoTeamBalance"} > 0) {
			    $g_servers{$s_addr}->set("ba_enabled", "1");
  			    &printEvent("TEAMS", "Auto-Team-Balancing is enabled", 1);
			  } else {
			    $g_servers{$s_addr}->set("ba_enabled", "0");
  			    &printEvent("TEAMS", "Auto-Team-Balancing is disabled", 1);
			  }
			  if ($s_cfg{"AutoBanRetry"} > 0) {
			    $g_servers{$s_addr}->set("auto_ban", "1");
  		    	&printEvent("TEAMS", "Auto-Retry-Banning is enabled", 1);
			  } else {
			    $g_servers{$s_addr}->set("auto_ban", "0");
  			    &printEvent("TEAMS", "Auto-Retry-Banning is disabled", 1);
			  }
			  
			  if ($s_cfg{"MinimumPlayersRank"} > 0) {
			    $g_servers{$s_addr}->set("min_players_rank", $s_cfg{"MinimumPlayersRank"});
  		    	&printEvent("SERVER", "Requires minimum players rank is enabled [MinPos:".$s_cfg{"MinimumPlayersRank"}."]", 1);
			  } else {
			    $g_servers{$s_addr}->set("min_players_rank", "0");
  			    &printEvent("SERVER", "Requires minimum players rank is disabled", 1);
			  }

			  if ($s_cfg{"Admins"} ne "") {
			    @{$g_servers{$s_addr}->{admins}} = split(/,/, $s_cfg{"Admins"});
  		    	&printEvent("SERVER", "Admins: ".$s_cfg{"Admins"}, 1);
			  }

			  if ($s_cfg{"SwitchAdmins"} > 0) {
			    $g_servers{$s_addr}->set("switch_admins", "1");
  		    	&printEvent("TEAMS", "Switching Admins on Auto-Team-Balance is enabled", 1);
			  } else {
			    $g_servers{$s_addr}->set("switch_admins", "0");
  		    	&printEvent("TEAMS", "Switching Admins on Auto-Team-Balance is disabled", 1);
			  }

			  if ($s_cfg{"IgnoreBots"} > 0) {
			    $g_servers{$s_addr}->set("ignore_bots", "1");
  		    	&printEvent("SERVER", "Ignoring bots is enabled", 1);
			  } else {
			    $g_servers{$s_addr}->set("ignore_bots", "0");
  			    &printEvent("SERVER", "Ignoring bots is disabled", 1);
			  }
        	  $g_servers{$s_addr}->set("skill_mode", $s_cfg{"SkillMode"});
  	    	  &printEvent("SERVER", "Using skill mode ".$s_cfg{"SkillMode"}, 1);

			  if ($s_cfg{"GameType"} == 1) {
          	    $g_servers{$s_addr}->set("game_type", $s_cfg{"GameType"});
    	    	&printEvent("SERVER", "Game type: Counter-Strike: Source - Deathmatch", 1);
			  } else {
        	    $g_servers{$s_addr}->set("game_type", "0");
    	    	&printEvent("SERVER", "Game type: Normal", 1);
			  }

        	  $g_servers{$s_addr}->set("mod", $s_cfg{"Mod"});
        	  
        	  if ($s_cfg{"Mod"} ne "") {
    	    	  &printEvent("SERVER", "Using plugin ".$s_cfg{"Mod"}." for internal functions!", 1);
    	      }  

   		   }
   		   
		}

		if (($g_stdin == 0) && ($g_servers{$s_addr}->get("last_event") > 0) && ( (time() - $g_servers{$s_addr}->get("last_event")) > 300) ) {
  		  $g_servers{$s_addr}->set("map", "");
          $g_servers{$s_addr}->get_map();
		}
		$g_servers{$s_addr}->set("last_event", time());
		
		# Get the datestamp (or complain)
		if ($s_output =~ s/^.*L (\d\d)\/(\d\d)\/(\d{4}) - (\d\d):(\d\d):(\d\d):\s*//)
		{
			$ev_month = $1;
			$ev_day   = $2;
			$ev_year  = $3;
			$ev_hour  = $4;
			$ev_min   = $5;
			$ev_sec   = $6;
			$ev_time  = "$ev_hour:$ev_min:$ev_sec";
		
			if ($g_timestamp){
				$ev_timestamp = "$ev_year-$ev_month-$ev_day $ev_time";
				$ev_datetime  = "'$ev_timestamp'";
				$ev_unixtime  = timelocal($ev_sec,$ev_min,$ev_hour,$ev_day,$ev_month-1,$ev_year);
			}
		} else {
			&printEvent(998, "MALFORMED DATA: " . $s_output);
			next;
		}

		# Now we parse the events.
		
		my $ev_type   = 0;
		my $ev_status = "";
		my $ev_team   = "";
		my $ev_player = 0;
		my $ev_verb   = "";
		my $ev_obj_a  = "";
		my $ev_obj_b  = "";
		my $ev_obj_c  = "";
		my $ev_properties = "";
		my %ev_properties = ();
		my %ev_player = ();
	
		if ($s_output =~ /^"([^"]+)" ([^"\(]+) "([^"]+)" [^"\(]+ "([^"]+)"(.*)$/)
		{
			# Prototype: "player" verb "obj_a" ?... "obj_b"[properties]
			# Matches:
			#  8. Kills
			#  9. Injuring
			# 10. Player-Player Actions
			# 11. Player Objectives/Actions
			
			# 5:10:33 "nubZtar [NL]<45><STEAM_0:1:5665787><CT>" killed "Jesper [SWE]<44><STEAM_0:1:5341053><TERRORIST>" with "ak47" (headshot)
	        # Team "TERRORIST" triggered "Terrorists_Win" (CT "12") (T "12")		
	        
			$ev_player = $1;
			$ev_verb   = $2; # killed; attacked; triggered
			$ev_obj_a  = $3; # victim; action
			$ev_obj_b  = $4; # weapon; victim
			$ev_properties = $5;
			
			%ev_properties = &getProperties($ev_properties);
			
			if (like($ev_verb, "killed"))
			{
				my $killerinfo = &getPlayerInfo($ev_player, 1);
				my $victiminfo = &getPlayerInfo($ev_obj_a, 1);
				
				
				
				$ev_type = 8;
				$headshot = 0;
	    		if ($ev_properties eq " (headshot)") {
				  $headshot = 1;
				}
	
  				if ($killerinfo && $victiminfo)
				{
				  my $killerId       = $killerinfo->{"userid"};
				  my $killerUniqueId = $killerinfo->{"uniqueid"};
                  my $killer         = $g_players{"$s_addr/$killerId/$killerUniqueId"};
                  if (($killer) && ($killerinfo->{"team"} ne "") && ($killer->get("team") ne $killerinfo->{"team"}) ) 	{
                    $killer->set("team", $killerinfo->{"team"});
  			        $killer->updateDB();
			        $killer->updateTimestamp();
    	   	      }  
				  my $victimId       = $victiminfo->{"userid"};
				  my $victimUniqueId = $killerinfo->{"uniqueid"};
                  my $victim         = $g_players{"$s_addr/$victimId/$victimUniqueId"};
                  if (($victim) && ($victiminfo->{"team"} ne "") && ($victim->get("team") ne $victiminfo->{"team"}) ) 	{
                    $victim->set("team", $victiminfo->{"team"});
  			        $victim->updateDB();
			        $victim->updateTimestamp();
    	   	      }  
				  $ev_status = &doEvent_Frag(
					$killerinfo->{"userid"},
					$killerinfo->{"uniqueid"},
					$victiminfo->{"userid"},
					$victiminfo->{"uniqueid"},
					$ev_obj_b,
					$headshot
				  );
				}  
			}
			elsif (like($ev_verb, "attacked"))
			{
				$ev_type = 9;
				$ev_status = "(IGNORED) $s_output";
			}
			elsif (like($ev_verb, "triggered"))
			{
	  			my $playerinfo = &getPlayerInfo($ev_player, 1);
				my $victiminfo = &getPlayerInfo($ev_obj_b, 1);
				
				$ev_type = 10;
				
				if ($playerinfo && $victiminfo)
				{
					$ev_status = &doEvent_PlayerPlayerAction(
						$playerinfo->{"userid"},
						$playerinfo->{"uniqueid"},
						$victiminfo->{"userid"},
						$victiminfo->{"unqiueid"},
						$ev_obj_a
					);
				}
			}
			elsif (like($ev_verb, "triggered a"))
			{
				my $playerinfo = &getPlayerInfo($ev_player, 1);
	
				$ev_type = 11;
	
				if ($playerinfo)
				{
					$ev_status = &doEvent_PlayerAction(
						$playerinfo->{"userid"},
						$playerinfo->{"uniqueid"},
						$ev_obj_a
					);
				}
			}
		}
		elsif ( $s_output =~ /^(?:\[STATSME\] )?"([^"]+)" triggered "(weaponstats\d{0,1})"(.*)$/ )
		{
			# Prototype: [STATSME] "player" triggered "weaponstats?"[properties]
			# Matches:
			# 501. Statsme weaponstats
			# 502. Statsme weaponstats2
	
			$ev_player = $1;
			$ev_verb   = $2; # weaponstats; weaponstats2
			$ev_properties = $3;
	
			%ev_properties = &getProperties($ev_properties);
	
			if (like($ev_verb, "weaponstats"))
			{
				$ev_type = 501;
	
				my $playerinfo = &getPlayerInfo($ev_player, 0);
	
				if ($playerinfo)
				{
					$ev_status = &doEvent_Statsme(
						$playerinfo->{"userid"},
						$playerinfo->{"uniqueid"},
						$ev_properties{"weapon"},
						$ev_properties{"shots"},
						$ev_properties{"hits"},
						$ev_properties{"headshots"},
						$ev_properties{"damage"},
						$ev_properties{"kills"},
						$ev_properties{"deaths"}
					);
				}
			}
			elsif (like($ev_verb, "weaponstats2"))
			{
				$ev_type = 502;
	
				my $playerinfo = &getPlayerInfo($ev_player, 0);
	
				if ($playerinfo)
					{
		
					$ev_status = &doEvent_Statsme2(
						$playerinfo->{"userid"},
						$playerinfo->{"uniqueid"},
						$ev_properties{"weapon"},
						$ev_properties{"head"},
						$ev_properties{"chest"},
						$ev_properties{"stomach"},
						$ev_properties{"leftarm"},
						$ev_properties{"rightarm"},
						$ev_properties{"leftleg"},
						$ev_properties{"rightleg"}
					);
				}
			}
		}
		elsif ( $s_output =~ /^(?:\[STATSME\] )?"([^"]+)" triggered "(latency|time)"(.*)$/ )
		{
			# Prototype: [STATSME] "player" triggered "latency|time"[properties]
			# Matches:
			# 503. Statsme latency
			# 504. Statsme time
	
			$ev_player = $1;
			$ev_verb   = $2; # latency; time
			$ev_properties = $3;
	
			%ev_properties = &getProperties($ev_properties);
	
			if (like($ev_verb, "latency"))
			{
				$ev_type = 503;
	
				my $playerinfo = &getPlayerInfo($ev_player, 0);
	
				if ($playerinfo)
				{
					$ev_status = &doEvent_Statsme_Latency(
						$playerinfo->{"userid"},
						$playerinfo->{"uniqueid"},
						$ev_properties{"ping"}
					);
				}
			}
			elsif (like($ev_verb, "time"))
			{
				$ev_type = 504;
	
				my $playerinfo = &getPlayerInfo($ev_player, 0);
	
				if ($playerinfo)
				{
					my ($min, $sec) = split(/:/, $ev_properties{"time"});
	
					my $hour = sprintf("%d", $min / 60);
	
					if ($hour) {
						$min = $min % 60;
					}
	
					$ev_status = &doEvent_Statsme_Time(
						$playerinfo->{"userid"},
						$playerinfo->{"uniqueid"},
						"$hour:$min:$sec"
					);
				}
			}
		}
		elsif ($s_output =~ /^"([^"]+)" ([^"\(]+) "([^"]+)"(.*)$/)
		{
			# Prototype: "player" verb "obj_a"[properties]
			# Matches:
			#  1. Connection
			#  4. Suicides
			#  5. Team Selection
			#  6. Role Selection
			#  7. Change Name
			# 11. Player Objectives/Actions
			# 14. a) Chat; b) Team Chat
			
			$ev_player = $1;
			$ev_verb   = $2;
			$ev_obj_a  = $3;
			$ev_properties = $4;
			
			%ev_properties = &getProperties($ev_properties);
			

			if (like($ev_verb, "connected, address"))
			{
				my $ipAddr = $ev_obj_a;
				my $playerinfo;
				
				if ($ipAddr =~ /([\d.]+):(\d+)/)
				{
					$ipAddr = $1;
				}
				
				if ($g_mode eq "LAN")
				{
					$playerinfo = &getPlayerInfo($ev_player, 1, $ipAddr);
				}
				else
				{
					$playerinfo = &getPlayerInfo($ev_player, 1);
				}
				
				$ev_type = 1;
				
				if ($playerinfo)
				{
					if (($playerinfo->{"uniqueid"} =~ /UNKNOWN/) || ($playerinfo->{"uniqueid"} =~ /PENDING/) || ($playerinfo->{"uniqueid"} =~ /VALVE_ID_LAN/))
					{
						$ev_status = "(DELAYING CONNECTION): $s_output";
	
	                    if ($g_mode ne "LAN")  {
  					      my $p_name   = $playerinfo->{"name"};
  					      my $p_userid = $playerinfo->{"userid"};
					      &printEvent("SERVER", "LATE CONNECT [$p_name/$p_userid] - STEAM_ID_PENDING");
						  $g_preconnect->{"$s_addr/$p_userid/$p_name"} = {
							ipaddress => $ipAddr,
							name => $p_name,
							server => $s_addr,
							timestamp => time()
						  };
						}   
					}
					else
					{
						$ev_status = &doEvent_Connect(
							$playerinfo->{"userid"},
							$playerinfo->{"uniqueid"},
							$ipAddr
						);
					}
				}
			}
			elsif (like($ev_verb, "committed suicide with"))
			{
				my $playerinfo = &getPlayerInfo($ev_player, 1);
				
				$ev_type = 4;
				
				if ($playerinfo)
				{
					$ev_status = &doEvent_Suicide(
						$playerinfo->{"userid"},
						$playerinfo->{"uniqueid"},
						$ev_obj_a
					);
				}
			}
			elsif (like($ev_verb, "joined team"))
			{
				my $playerinfo = &getPlayerInfo($ev_player, 1);
				
				$ev_type = 5;
				
				if ($playerinfo)
				{
					$ev_status = &doEvent_TeamSelection(
						$playerinfo->{"userid"},
						$playerinfo->{"uniqueid"},
						$ev_obj_a
					);
				}
			}
			elsif (like($ev_verb, "changed role to"))
			{
				my $playerinfo = &getPlayerInfo($ev_player, 1);
				
				$ev_type = 6;
				
				if ($playerinfo)
				{
					$ev_status = &doEvent_RoleSelection(
						$playerinfo->{"userid"},
						$playerinfo->{"uniqueid"},
						$ev_obj_a
					);
				}
			}
			elsif (like($ev_verb, "changed name to"))
			{
				my $playerinfo = &getPlayerInfo($ev_player, 1);
				
				$ev_type = 7;
				
				if ($playerinfo)
				{
					$ev_status = &doEvent_ChangeName(
						$playerinfo->{"userid"},
						$playerinfo->{"uniqueid"},
						$ev_obj_a
					);
				}
			}
			elsif (like($ev_verb, "triggered"))
			{
			
			    # in cs:s players dropp the bomb if they are the only ts
			    # and disconnect...the dropp the bomb after they disconnected :/
			    if ($ev_obj_a eq "Dropped_The_Bomb")
			    {
				  $playerinfo = &getPlayerInfo($ev_player, 0);
			    }
			    else
			    {
				  $playerinfo = &getPlayerInfo($ev_player, 1);
				}  
				$ev_type = 11;
				
				if ($playerinfo)
				{
					$ev_status = &doEvent_PlayerAction(
						$playerinfo->{"userid"},
						$playerinfo->{"uniqueid"},
						$ev_obj_a
					);
				}
				
				
			}
			elsif (like($ev_verb, "triggered a"))
			{
				my $playerinfo = &getPlayerInfo($ev_player, 1);
				
				$ev_type = 11;
				
				if ($playerinfo)
				{
					$ev_status = &doEvent_PlayerAction(
						$playerinfo->{"userid"},
						$playerinfo->{"uniqueid"},
						$ev_obj_a
					);
				}
			}
			elsif (like($ev_verb, "say"))
			{
				my $playerinfo = &getPlayerInfo($ev_player, 1);
				
				$ev_type = 14;
				
				if ($playerinfo)
				{
					$ev_status = &doEvent_Chat(
						"say",
						$playerinfo->{"userid"},
						$playerinfo->{"uniqueid"},
						$ev_obj_a
					);
				}
			}
			elsif (like($ev_verb, "say_team"))
			{
				my $playerinfo = &getPlayerInfo($ev_player, 1);
				
				$ev_type = 14;
				
				if ($playerinfo)
				{
					$ev_status = &doEvent_Chat(
						"say_team",
						$playerinfo->{"userid"},
						$playerinfo->{"uniqueid"},
						$ev_obj_a
					);
				}
			}
		}
		elsif ($s_output =~ /^"([^"]+)" ([^\(]+)(.*)$/)
		{
			# Prototype: "player" verb[properties]
			# Matches:
			#  2. Enter Game
			#  3. Disconnection
			
			$ev_player = $1;
			$ev_verb   = $2;
			$ev_properties = $3;
			
			%ev_properties = &getProperties($ev_properties);
			
			if (like($ev_verb, "entered the game"))
			{
				my $playerinfo = &getPlayerInfo($ev_player, 1);
				
				if ($playerinfo)
				{
					$ev_type = 2;
					$ev_status = &doEvent_EnterGame($playerinfo->{"userid"}, $playerinfo->{"uniqueid"}, $ev_obj_a);
				}
			}
			elsif (like($ev_verb, "disconnected"))
			{
				my $playerinfo = &getPlayerInfo($ev_player, 0);
				
				if ($playerinfo)
				{
					$ev_type = 3;
	
					$userid   = $playerinfo->{"userid"};
					$uniqueid = $playerinfo->{"uniqueid"};
	
					if ($g_players{"$s_addr/$userid/$uniqueid"})
					{
	                  $g_servers{$s_addr}->{numplayers}--;
	                  $g_servers{$s_addr}->updateDB();
	                }  
	
					$ev_status = &doEvent_Disconnect(
						$playerinfo->{"userid"},
						$playerinfo->{"name"},
						$playerinfo->{"uniqueid"},
						$ev_properties
					);
				}
			}
			elsif (like($ev_verb, "STEAM USERID validated") || like($ev_verb, "VALVE USERID validated"))
			{               
				my $playerinfo = &getPlayerInfo($ev_player, 0);
	
				if ($playerinfo)        
				{                       
					$ev_type = 1;
					
				}
			}       
		}
		elsif ($s_output =~ /^Team "([^"]+)" ([^"\(]+) "([^"]+)" [^"\(]+ "([^"]+)" [^"\(]+(.*)$/)
		{
			# Prototype: Team "team" verb "obj_a" ?... "obj_b" ?...[properties]
			# Matches:
		    # 16. Round-End Team Score Report
			
			$ev_team   = $1;
			$ev_verb   = $2;
			$ev_obj_a  = $3;
			$ev_obj_b  = $4;
			$ev_properties = $5;
			
			%ev_properties = &getProperties($ev_properties);
			
			if (like($ev_verb, "scored"))
			{
				$ev_type = 16;
				$ev_status = &doEvent_TeamScoreReport(
					$ev_team,
					$ev_obj_a,
					$ev_obj_b
				);
			}
		}
		elsif ($s_output =~ /^Team "([^"]+)" ([^"\(]+) "([^"]+)"(.*)$/)
		{
			# Prototype: Team "team" verb "obj_a"[properties]
			# Matches:
		    # 12. Team Objectives/Actions
			# 15. Team Alliances
			
			$ev_team   = $1;
			$ev_verb   = $2;
			$ev_obj_a  = $3;
			$ev_properties = $4;
			
			%ev_properties = &getProperties($ev_properties);
			
			if (like($ev_verb, "triggered"))
			{
				$ev_type = 12;
				$ev_status = &doEvent_TeamAction(
					$ev_team,
					$ev_obj_a
				);
			}
			elsif (like($ev_verb, "triggered a"))
			{
				$ev_type = 12;
				$ev_status = &doEvent_TeamAction(
					$ev_team,
					$ev_obj_a
				);
			}
			elsif (like($ev_verb, "formed alliance with team"))
			{
				$ev_type = 15;
				$ev_status = &doEvent_TeamAlliance(
					$ev_team,
					$ev_obj_a
				);
			}
		}
		elsif ($s_output =~ /^([^"\(]+) "([^"]+)" = "([^"]+)"(.*)$/)
		{
			# Prototype: verb "obj_a" = "obj_b"[properties]
			# Matches:
		    # 17. b) Server cvar "var" = "value"
			
			$ev_verb   = $1;
			$ev_obj_a  = $2;
			$ev_obj_b  = $3;
			$ev_properties = $4;
			
			%ev_properties = &getProperties($ev_properties);
			
			if (like($ev_verb, "Server cvar"))
			{
				$ev_type = 17;
				$ev_status = &doEvent_ServerCvar(
					"var",
					$ev_obj_a,
					$ev_obj_b
				);
			}
		}
		elsif ($s_output =~ /^(Rcon|Bad Rcon): "rcon [^"]+"([^"]+)"\s+(.+)" from "([^"]+)"(.*)$/)
		{
			# Prototype: verb: "rcon ?..."obj_a" obj_b" from "obj_c"[properties]
			# Matches:
		    # 20. a) Rcon; b) Bad Rcon
			
			$ev_verb   = $1;
			$ev_obj_a  = $2; # password
			$ev_obj_b  = $3; # command
			$ev_obj_c  = $4; # ip:port
			$ev_properties = $5;
			
			%ev_properties = &getProperties($ev_properties);
			
			if (like($ev_verb, "Rcon"))
			{
				$ev_type = 20;
				$ev_status = &doEvent_Rcon(
					"OK",
					$ev_obj_b,
					$ev_obj_a,
					$ev_obj_c
				);
			}
			elsif (like($ev_verb, "Bad Rcon"))
			{
				$ev_type = 20;
				$ev_status = &doEvent_Rcon(
					"BAD",
					$ev_obj_b,
					$ev_obj_a,
					$ev_obj_c
				);
			}
		}
		elsif ($s_output =~ /^([^"\(]+) "([^"]+)"(.*)$/)
		{
			# Prototype: verb "obj_a"[properties]
			# Matches:
			# 13. World Objectives/Actions
			# 19. a) Loading map; b) Started map
			# 21. Server Name
			
			$ev_verb   = $1;
			$ev_obj_a  = $2;
			$ev_properties = $3;
			
			%ev_properties = &getProperties($ev_properties);
			
			if (like($ev_verb, "World triggered"))
			{
				$ev_type = 13;
				$ev_status = &doEvent_WorldAction(
					$ev_obj_a
				);
			}
			elsif (like($ev_verb, "Loading map"))
			{
				$ev_type = 19;
				$ev_status = &doEvent_ChangeMap(
					"loading",
					$ev_obj_a
				);
			}
			elsif (like($ev_verb, "Started map"))
			{
				$ev_type = 19;
				$ev_status = &doEvent_ChangeMap(
					"started",
					$ev_obj_a
				);
			}
			elsif (like($ev_verb, "Server name is"))
			{
				$ev_type = 21;
				$ev_status = &doEvent_ServerName(
					$ev_obj_a
				);
			}
		}
		elsif ($s_output =~ /^((?:Server cvars|Log file)[^\(]+)(.*)$/)
		{
			# Prototype: verb[properties]
			# Matches:
		    # 17. a) Server cvars start; c) Server cvars end
			# 18. a) Log file started; b) Log file closed
			
			$ev_verb   = $1;
			$ev_properties = $2;
			
			%ev_properties = &getProperties($ev_properties);
			
			if (like($ev_verb, "Server cvars start"))
			{
				$ev_type = 17;
				$ev_status = &doEvent_ServerCvar(
					"start"
				);
			}
			elsif (like($ev_verb, "Server cvars end"))
			{
				$ev_type = 17;
				$ev_status = &doEvent_ServerCvar(
					"end"
				);
			}
			elsif (like($ev_verb, "Log file started"))
			{
				$ev_type = 18;
				$ev_status = &doEvent_LogFile(
					"start"
				);
			}
			elsif (like($ev_verb, "Log file closed"))
			{
				$ev_type = 18;
				$ev_status = &doEvent_LogFile(
					"end"
				);
			}
		}
		elsif ($s_output =~ /^\[MANI_ADMIN_PLUGIN\]\s*(.+)$/)
		{
			# Prototype: [MANI_ADMIN_PLUGIN] obj_a
			# Matches:
		    # Mani-Admin-Plugin messages
			
			$ev_obj_a  = $1;
			
			$ev_type = 500;
			$ev_status = &doEvent_Admin(
				"Mani Admin Plugin",
				$ev_obj_a
			);
		}
		elsif ($s_output =~ /^\[BeetlesMod\]\s*(.+)$/)
		{
			# Prototype: Cmd:[BeetlesMod] obj_a
			# Matches:
		    # Beetles Mod messages
			
			$ev_obj_a  = $1;
			
			$ev_type = 500;
			$ev_status = &doEvent_Admin(
				"Beetles Mod",
				$ev_obj_a
			);
		}
		elsif ($s_output =~ /^\[ADMIN:(.+)\] ADMIN Command: \1 used command (.+)$/)
		{
			# Prototype: [ADMIN] obj_a
			# Matches:
		    # Admin Mod messages
			
			$ev_obj_a  = $1;
			$ev_obj_b  = $2;
			
			$ev_type = 500;
			$ev_status = &doEvent_Admin(
				"Admin Mod",
				$ev_obj_b,
				$ev_obj_a
			);
		}
		
	
		if ($ev_type)
		{
			if ($g_debug > 2)
			{
print <<EOT
type   = "$ev_type"
team   = "$ev_team"
player = "$ev_player"
verb   = "$ev_verb"
obj_a  = "$ev_obj_a"
obj_b  = "$ev_obj_b"
obj_c  = "$ev_obj_c"
properties = "$ev_properties"
EOT
;
				while (my($key, $value) = each(%ev_properties))
				{
					print "property: \"$key\" = \"$value\"\n";
				}
				
				while (my($key, $value) = each(%ev_player))
				{
					print "player $key = \"$value\"\n";
				}
			}
			
			if ($ev_status ne "")
			{
				&printEvent($ev_type, $ev_status);
			}
			else
			{
				&printEvent($ev_type, "BAD DATA: $s_output");
			}
		}
		elsif (($s_output =~ /^Banid: "([^"]+)" was kicked and banned "for ([0-9]+).00 minutes" by "Console"$/) ||
               ($s_output =~ /^Banid: "([^"]+)" was kicked and banned "(permanently)" by "Console"$/) ||
		       ($s_output =~ /^Banid: "([^"]+)" was banned "for ([0-9]+).00 minutes" by "Console"$/) ||
               ($s_output =~ /^Banid: "([^"]+)" was banned "(permanently)" by "Console"$/))
		{
			# Prototype: "player" verb[properties]
    		# Banid: "�~T huaaa<1804><STEAM_0:1:10769><>" was kicked and banned "permanently" by "Console"
			
           $ev_player  = $1;
		   $ev_bantime = $2;
 		   my $playerinfo = &getPlayerInfo($ev_player, 1);
   		    
		   if ($ev_bantime eq "5") {
			 &printEvent("BAN", "Auto Ban - ignored");
		   } elsif ($playerinfo) {
		     if (($g_global_banning > 0) && ($g_servers{$s_addr}->{ignore_nextban}->{$playerinfo->{"uniqueid"}} == 1)) {
   		       delete($g_servers{$s_addr}->{ignore_nextban}->{$playerinfo->{"uniqueid"}});
			   &printEvent("BAN", "Global Ban - ignored");
    		 } elsif (!$g_servers{$s_addr}->{ignore_nextban}->{$playerinfo->{"uniqueid"}}) {
 			     my $playerId   = $playerinfo->{"userid"};
                 my $p_steamid  = $playerinfo->{"uniqueid"};
                 my $p_is_bot   = $playerinfo->{"is_bot"};
			     my $player_obj = $g_players{"$s_addr/$playerId/$p_steamid"};
  			     &printEvent("BAN", "Steamid: ".$p_steamid);

  			     if ($player_obj) {
   	               $player_obj->{"is_banned"} = 1;
			     }  

  	    	     if ($g_global_banning > 0) {
  	    	       if ($ev_bantime eq "permanently") {
                     my $result = &doQuery("SELECT playerId FROM hlstats_PlayerUniqueIds WHERE uniqueId='".$p_steamid."'");
                     $player_id = $result->fetchrow_array;
                     if (($player_id ne "") && ($p_is_bot == 0)) {
        			   &printEvent("BAN", "Hide player!");
                       &doQuery("UPDATE hlstats_Players SET hideranking=1 WHERE playerId='".$player_id."'");
                     }
  	    	         $ev_bantime = 0;
  	    	       }
                   while (my($addr, $server) = each(%g_servers)) {
                     if ($addr ne $s_addr) {
                       if (($p_steamid ne "") && ($p_is_bot == 0)) {
          			     &printEvent("BAN", "Global banning on ".$addr);
            		     $server->{ignore_nextban}->{$playerinfo->{"uniqueid"}} = 1;
                         $server->dorcon("banid ".$ev_bantime." $p_steamid");
               	         $server->dorcon("writeid");
               	       }  
                     }
                   } 
                 }  
             }  
		   } else {
  			 &printEvent("BAN", "No playerinfo");
		   }
	  	   &printEvent("BAN", $s_output);
        }
 		else
		{
			# Unrecognized event
			&printEvent(999, "UNRECOGNIZED: " . $s_output);
		}
		
  	  if (($g_stdin == 0) && ($g_servers{$s_addr}))
	  {
	    $s_lines = $g_servers{$s_addr}->get("lines");
		# get ping from players
		if ($s_lines % 1000 == 0) {
		  $g_servers{$s_addr}->update_players_pings();
		}
		
    	if ($g_servers{$s_addr}->get("broadcasting_events") == 1) {
          if ($s_lines % 1000 == 0)	{
            if ($g_servers{$s_addr}->get("broadcasting_command_announce") ne "") {
		      $g_servers{$s_addr}->dorcon($g_servers{$s_addr}->get("broadcasting_command_announce")." Type help to show all available commands!");
            } else {
       	      if ($g_servers{$s_addr}->get("broadcasting_command_steamid") == 1) {
                foreach $player (values(%g_players))	{
                  if (($player->{server} eq $s_addr) && ($player->{is_bot} == 0))  {
                    my $p_userid  = $player->get("userid");
   		            $g_servers{$s_addr}->dorcon($g_servers{$s_addr}->get("broadcasting_command")." \"$p_userid\" Type help to show all available commands!");
                  }
                }  
        	  } else {
	   	        $g_servers{$s_addr}->dorcon($g_servers{$s_addr}->get("broadcasting_command")." Type help to show all available commands!");
	  	      }  
	  	    }  
	  	  }  
	  	}  

	    if ($g_servers{$s_addr}->get("broadcasting_events") == 1) {
          if ($s_lines % 1600 == 0)	{
            if ($g_servers{$s_addr}->get("broadcasting_command_announce") ne "") {
              if ($g_servers{$s_addr}->get("contact") ne "") {
   		        $g_servers{$s_addr}->dorcon($g_servers{$s_addr}->get("broadcasting_command_announce")." Admin email address: ".$g_servers{$s_addr}->get("contact"));
   		      }  
            } else {
       	      if ($g_servers{$s_addr}->get("broadcasting_command_steamid") == 1) {
       	        if ($g_servers{$s_addr}->get("contact") ne "") {
                  foreach $player (values(%g_players))	{
                    if (($player->{server} eq $s_addr) && ($player->{is_bot} == 0))  {
                      my $p_userid  = $player->get("userid");
  	                  $g_servers{$s_addr}->dorcon($g_servers{$s_addr}->get("broadcasting_command")." \"$p_userid\" Admin-Contact: ".$g_servers{$s_addr}->get("contact"));
                    }
                  }  
                }  
     	      } else {
 		        $g_servers{$s_addr}->dorcon($g_servers{$s_addr}->get("broadcasting_command")." Admin-Contact: ".$g_servers{$s_addr}->get("contact"));
	  	      }  
	  	    }  
	  	  }  
	    }
		if ($g_servers{$s_addr}->get("show_stats") == 1)
		{
			# show stats
			if ($s_lines % 2500 == 0) {
			  $g_servers{$s_addr}->dostats();
			}
		}
		
		if ($g_servers{$s_addr}->get("broadcasting_serverdata") > 0)
		{
			# send stats to masterserver
			if (($s_lines % $g_servers{$s_addr}->get("broadcasting_server_interval") == 0)) {
			  $g_servers{$s_addr}->send_heartbeat();
			}
 		}	
 		
   	    if (($g_stdin == 0) && (($s_lines > 0) && ($s_lines % 120000 == 0)))	{
			# Delete events over $g_deletedays days old
			if ($g_debug > 0)
			{
				&printEvent("MYSQL", "Cleaning up database: deleting events older than $g_deletedays days ...", 1);
			}
			
			my $deleteType = "";
			$deleteType = " LOW_PRIORITY" if ($db_lowpriority);
			
			foreach $eventTable (keys(%g_eventTables))
			{
				&doQuery("
					DELETE$deleteType FROM
						hlstats_Events_$eventTable
					WHERE
						eventTime < DATE_SUB(CURRENT_TIMESTAMP(), INTERVAL $g_deletedays DAY)
				");
			}

	    	&doQuery("
				DELETE$deleteType FROM
					hlstats_Players_History
				WHERE
					eventTime < DATE_SUB(CURRENT_TIMESTAMP(), INTERVAL $g_deletedays DAY)
			");
			
			if ($g_debug > 0)
			{
	 		  &printEvent("MYSQL", "Cleanup complete", 1);
			}
		}	
 		
   	    if ($s_lines > 500000)	{
          $g_servers{$s_addr}->set("lines", 0);	
	    }  else	{
	      $g_servers{$s_addr}->increment("lines");	
	    } 
	  }	

	} else {
	  $s_addr = "";
	}
	
	
	# Clean up
	while ( my($pl, $player) = each(%g_players) )
	{
	    my $timeout = 250;
	    if ($g_mode eq "LAN")  {
	      $timeout = $timeout * 2;
	    }  

	    my $p_is_bot = $player->get("is_bot");
	    if (($p_is_bot == 0) && (($ev_unixtime - $player->get("timestamp")) > ($timeout - 20))) {
	      &printEvent(400, $player->getInfoString()." is ".($ev_unixtime - $player->get("timestamp"))."s idle");
	    }  
	    
	    
		if ( ($ev_unixtime - $player->get("timestamp")) > $timeout )
		{
			# we delete any player who is inactive for over $timeout sec
			# - they probably disconnected silently somehow.
			if (($p_is_bot == 0) || ($g_stdin)) {
  			  my($server) = split(/\//, $pl);
			  &printEvent(400, "Auto-disconnecting " . $player->getInfoString() ." for idling (" . ($ev_unixtime - $player->get("timestamp")) . " sec) on server (".$server.")");
			  $player->updateDB();
   	          $player->deleteLivestats();
              $g_servers{$server}->{numplayers}--;
			  $g_servers{$server}->updateDB();
			  delete($g_players{$pl});
			}  
		}
	}  

	while ( my($pl, $player) = each(%g_preconnect) )
	{
	  my $timeout = 600;
      if ( ($ev_unixtime - $player->{"timestamp"}) > $timeout )
	  {
  	    &printEvent(401, "Clearing pre-connect entry with key ".$pl);
	    delete($g_preconnect{$pl});
	  }
	}
	
	if ($g_stdin == 0)  {
      # Track the Trend
 	  if ($g_track_stats_trend > 0)  {
        track_hlstats_trend();
      }  
  	  while (my($addr, $server) = each(%g_servers)) {
		# send stats to masterserver
		if ($server) {
      	  $server->track_server_load();
		  if ($server->get("broadcasting_serverdata") > 0) {
 		    my $last_heartbeat = $server->get("last_heartbeat");
		    if (($last_heartbeat < (time()-600)) || ($timeout == 15)) {
		      $server->send_heartbeat();
	 	    }
	 	  }  
	 	}  
	  }
	}
	
    if (($g_stdin == 0) && ($g_servers{$s_addr})) {
  	  if (($g_servers{$s_addr}->get("map") eq "") && (($timeout > 0) && ($timeout % 60 == 0))) {
        $g_servers{$s_addr}->get_map();
  	  }
  	}  
	
	# Delete events over $g_deletedays days old
	if (($g_stdin == 0) && (($timeout > 0) && ($timeout % 300 == 0)))
	{
 	    
		if ($g_debug > 0)
		{
			&printEvent("MYSQL", "Cleaning up database: deleting events older than $g_deletedays days ...", 1);
		}
		
		my $deleteType = "";
		$deleteType = " LOW_PRIORITY" if ($db_lowpriority);
		
		foreach $eventTable (keys(%g_eventTables))
		{
			&doQuery("
				DELETE$deleteType FROM
					hlstats_Events_$eventTable
				WHERE
					eventTime < DATE_SUB(CURRENT_TIMESTAMP(), INTERVAL $g_deletedays DAY)
			");
		}

    	&doQuery("
			DELETE$deleteType FROM
				hlstats_Players_History
			WHERE
				eventTime < DATE_SUB(CURRENT_TIMESTAMP(), INTERVAL $g_deletedays DAY)
		");
		
		if ($g_debug > 0)
		{
 		  &printEvent("MYSQL", "Cleanup complete", 1);
		}
	}

	if (($g_stdin == 0) && (($timeout > 0) && ($timeout % 600 == 0)))
	{
		if ($g_debug > 0)
		{
			&printEvent("MYSQL", "Optimizing database...", 1);
		}
		
		foreach $table (@g_allTables)
		{
			&doQuery("
				OPTIMIZE TABLE $table
			");
		}
		
		if ($g_debug > 0)
		{
			&printEvent("MYSQL", "Optimization complete.", 1);
		}		
	}
	$c++;
	$c = 1 if ($c > 500000);
	$import_logs_count++ if ($g_stdin);
	
}

$end_time = time();
if ($g_stdin)	{
  if ($import_logs_count > 0)  {
    print "\n";
  }  
  &doQuery("UPDATE hlstats_Players SET last_event=UNIX_TIMESTAMP();");
  &printEvent("IMPORT", "Import of log file complete. Scanned ".$import_logs_count." lines in ".($end_time-$start_time)." seconds", 1, 1);
}
