package HLstats_Player;
#
# 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.
#

#
# Constructor
#

sub new
{
	my $class_name = shift;
	my %params = @_;
	
	my $self = {};
	bless($self, $class_name);
	
	# Initialise Properties
	$self->{userid}            = 0;
	$self->{server}            = "";
	$self->{server_id}         = 1;
	$self->{name}              = "";
	$self->{uniqueid}          = "";
	$self->{plain_uniqueid}    = "";
	$self->{address}           = "";
	$self->{cli_port}          = "";
	$self->{ping}              = 0;
	$self->{connect_time}      = time();
	$self->{last_update}       = 0;
	$self->{last_update_skill} = 0;
	$self->{day_skill_change}  = 0;
	

	$self->{city}              = "";
	$self->{state}             = "";
	$self->{country}           = "";
	$self->{flag}              = "";
	$self->{lat}               = 0;
	$self->{lng}               = 0;
	
	$self->{playerid}          = 0;
	$self->{clan}              = 0;
	$self->{kills}             = 0;
	$self->{deaths}            = 0;
	$self->{suicides}          = 0;
	$self->{skill}             = 1000;
	$self->{game}              = 0;
	$self->{team}              = "";
	$self->{role}              = "";
	$self->{timestamp}         = 0;
	$self->{headshots}         = 0;
	$self->{shots}             = 0;
	$self->{hits}              = 0;
	
	$self->{auto_command}      = "";
	$self->{auto_type}         = "";
	$self->{auto_time}         = 0;
	$self->{auto_time_count}   = 0;
	
	$self->{session_skill}     = 0;
	$self->{session_kills}     = 0;
	$self->{session_deaths}    = 0;
	$self->{session_suicides}  = 0;
	$self->{session_headshots} = 0;
	$self->{session_shots}     = 0;
	$self->{session_hits}      = 0;
	$self->{session_start_pos} = -1;

	$self->{global_players}    = 0;
	$self->{global_rank}       = 0;
	$self->{global_skill}      = 0;
	$self->{global_kills}      = 0;
	$self->{global_deaths}     = 0;
	$self->{global_headshots}  = 0;

	$self->{map_kills}         = 0;
	$self->{map_deaths}        = 0;
	$self->{map_suicides}      = 0;
	$self->{map_headshots}     = 0;
	$self->{map_shots}         = 0;
	$self->{map_hits}          = 0;
	$self->{is_dead}           = 0;
	$self->{has_bomb}          = 0;
	
	$self->{is_banned}         = 0;
	$self->{is_bot}            = 0;

	$self->{display_events}    = 1;
	$self->{display_chat}      = 1;
	
	$self->{last_history_day}  = "";

	
	
	# Set Property Values
	
	die("HLstats_Player->new(): must specify player's uniqueid\n")
		unless (defined($params{uniqueid}));
	
	die("HLstats_Player->new(): must specify player's name\n")
		unless ($params{name} ne "");
	
	
	while (my($key, $value) = each(%params))
	{
		if ($key ne "name" && $key ne "uniqueid")
		{
			$self->set($key, $value);
		}
	}

	$self->{plain_uniqueid} = $params{uniqueid};
	$self->setUniqueId($params{uniqueid});
	$self->setName($params{name});
	
	$self->getAddress();
	$self->updateDB();



	&::printNotice("Created new player object " . $self->getInfoString());
	return $self;
}


#
# Set property 'key' to 'value'
#

sub set
{
	my ($self, $key, $value, $no_updatetime) = @_;
	
	if (defined($self->{$key}))
	{
		if ($no_updatetime == 0) {
			$self->{timestamp} = $::ev_unixtime;
		}
		
		if ($self->get($key) eq $value)
		{
			if ($g_debug > 2)
			{
				&::printNotice("Hlstats_Player->set ignored: Value of \"$key\" is already \"$value\"");
			}
			return 0;
		}
		
		if ($key eq "uniqueid")
		{
			return $self->setUniqueId($value);
		}
		elsif ($key eq "name")
		{
			return $self->setName($value);
		}
		else
		{
			$self->{$key} = $value;
			return 1;
		}
	}
	else
	{
		warn("HLstats_Player->set: \"$key\" is not a valid property name\n");
		return 0;
	}
}


#
# Increment (or decrement) the value of 'key' by 'amount' (or 1 by default)
#

sub increment
{
	my ($self, $key, $amount, $no_updatetime) = @_;
	
	$amount = int($amount);
	$amount = 1 if ($amount == 0);
	
	my $value = $self->get($key);
	$self->set($key, $value + $amount, $no_updatetime);
}


#
# Get value of property 'key'
#

sub get
{
	my ($self, $key) = @_;
	
	if (defined($self->{$key}))
	{
		return $self->{$key};
	}
	else
	{
		warn("HLstats_Player->get: \"$key\" is not a valid property name\n");
	}
}


sub check_history 
{
	my ($self, $playerId, $is_bot) = @_;

    my ($sec,$min,$hour,$mday,$mon,$year) = localtime(time());
    my $date = sprintf("%04d-%02d-%02d", $year+1900, $mon+1, $mday, $hour, $min, $sec);
	my $srv_addr  = $self->get("server");
	
	if (!$is_bot) {
	  $is_bot = 0;
	}  

	if (($::g_stdin == 0) && ($playerId > 0))
	{
      if (($is_bot == 0) || (($is_bot == 1) && ($::g_servers{$srv_addr}->get("ignore_bots") == 0))) {
        $self->{last_history_day} = sprintf("%02d", $mday);
		my $query = "
			SELECT
				playerId, skill_change
			FROM
				hlstats_Players_History
			WHERE
				playerId='" . $playerId . "'
				AND eventTime='".$date."'
				AND game='".$::g_servers{$srv_addr}->{game}."'
		";
		my $result = &::doQuery($query);
		
		if ($result->rows < 1)
		{
           my $query = "
	       INSERT INTO
  			hlstats_Players_History
        	(
		  		playerId,
		  		eventTime,
		  		game
	  	    )
			VALUES
			(
				'" . $playerId . "',
				'" . $date ."',
				'" . $::g_servers{$srv_addr}->{game} . "'
			)
		  ";
		  my $result = &::doQuery($query);
		  $result->finish;
		  $self->{day_skill_change}     = 0;
		} else {
		  my ($playerid, $skill_change) = $result->fetchrow_array;
          $self->{day_skill_change}     = $skill_change;
		}
        $result->finish;
      }  
	}	
}


#
# Set player's uniqueid
#

sub setUniqueId
{
	my ($self, $uniqueid) = @_;
	
	my $playerid = &::getPlayerId($uniqueid);
	
	if ($playerid)
	{
    	# An existing player. Get their skill rating.
		my $query = "
			SELECT
				skill, city, state, country, flag, lat, lng
			FROM
				hlstats_Players
			WHERE
				playerId='$playerid'
		";
		my $result = &::doQuery($query);
		($self->{skill}, $self->{city}, $self->{state},
		 $self->{country}, $self->{flag}, $self->{lat},	
		 $self->{lng}) = $result->fetchrow_array;
		$result->finish;
     	$self->{playerid} = $playerid;
        $self->{session_start_pos} = $self->getRank();
	}
	else
	{
     	my $srv_addr  = $self->get("server");
		# This is a new player. Create a new record for them in the Players
		# table.
		
		my $query = "
			INSERT INTO
				hlstats_Players
				(
					lastName,
					clan,
					game
				)
			VALUES
			(
				'" . &::quoteSQL($self->get("name")) . "',
				'" . $self->get("clan") . "',
				'" . $::g_servers{$srv_addr}->{game} . "'
			)
		";
		my $result = &::doQuery($query);
		$result->finish;
		
		$result = &::doQuery("SELECT LAST_INSERT_ID()");
		($playerid) = $result->fetchrow_array;
		$result->finish;
		
		if ($playerid)
		{
			$query = "
				INSERT INTO
					hlstats_PlayerUniqueIds
					(
						playerId,
						uniqueId,
						game
					)
				VALUES
				(
					'" . $playerid . "',
					'" . &::quoteSQL($uniqueid) . "',
					'" . $::g_servers{$srv_addr}->{game} . "'
				)
			";
			$result = &::doQuery($query);
			$result->finish;
		}
		else
		{
			error("Unable to create player:\n$query");
		}
	}
	
	$self->{uniqueid} = $uniqueid;
	$self->{playerid} = $playerid;

	if ($playerid > 0) {
	  $self->check_history($playerid);
	}
	
	return 1;
}



#
# Set player's name
#

sub setName
{
	my ($self, $name) = @_;
	
	my $oldname = $self->get("name");

	if ($oldname eq $name)
	{
		return 2;
	}
	
	if ($oldname)
	{
		$self->updateDB();
	}
	
	$self->{name} = $name;

	my $is_bot         = $self->get("is_bot");
    my $server_address = $self->get("server");
	if (($is_bot == 1) && ($::g_servers{$server_address}->get("ignore_bots") == 1)) {
	  $self->{clan} = "";
	} else {
  	  $self->{clan} = &::getClanId($name);
  	}  
	
	my $playerid = $self->get("playerid");
	
	if ($playerid)
	{
		my $query = "
			SELECT
				playerId
			FROM
				hlstats_PlayerNames
			WHERE
				playerId='" . $playerid . "'
				AND name='" . &::quoteSQL($self->get("name")) . "'
		";
		my $result = &::doQuery($query);
		
		if ($result->rows < 1)
		{
			$query = "
				INSERT INTO
					hlstats_PlayerNames
					(
						playerId,
						name,
						lastuse,
						numuses
					)
				VALUES
				(
					'" . $playerid . "',
					'" . &::quoteSQL($self->get("name")) . "',
					" . $::ev_datetime . ",
					1
				)
			";
			&::doQuery($query);
		}
		else
		{
			$query = "
				UPDATE
					hlstats_PlayerNames
				SET
					lastuse=" . $::ev_datetime . ",
					numuses=numuses+1
				WHERE
					playerId='" . $playerid . "'
					AND name='" . &::quoteSQL($self->get("name")) . "'
			";
			&::doQuery($query);
		}
		
		$result->finish;
	}
	else
	{
		&::error("HLstats_Player->setName(): No playerid");
	}
}



#
# Update player information in database
#

sub updateDB
{
	my ($self, $leaveLastUse, $callref) = @_;
	
	my $playerid  = $self->get("playerid");
	my $srv_addr  = $self->get("server");
	my $serverid  = $self->get("server_id");
	my $name      = $self->get("name");
	my $clan      = $self->get("clan");
	my $kills     = $self->get("kills");
	my $deaths    = $self->get("deaths");
	my $suicides  = $self->get("suicides");
	my $skill     = $self->get("skill");
	my $headshots = $self->get("headshots");
	my $shots     = $self->get("shots");
	my $hits      = $self->get("hits");
	
	my $team          = $self->get("team");
	my $map_kills     = $self->get("map_kills");
	my $map_deaths    = $self->get("map_deaths");
	my $map_suicides  = $self->get("map_suicides");
	my $map_headshots = $self->get("map_headshots");
	my $map_shots     = $self->get("map_shots");
	my $map_hits      = $self->get("map_hits");
	my $steamid       = $self->get("plain_uniqueid");
	
	my $is_dead       = $self->get("is_dead");
	my $has_bomb      = $self->get("has_bomb");
	my $ping          = $self->get("ping");
	my $connected     = $self->get("connect_time");
	my $skill_change  = $self->get("session_skill");
	
    my $add_connect_time = 0;
	if (($::g_stdin == 0) && ($self->get("last_update") > 0))  {
	  $add_connect_time = time() - $self->get("last_update");
	} elsif (($::g_stdin == 1) && ($self->get("last_update") > 0))  {
	  $add_connect_time = $::ev_unixtime - $self->get("last_update");
	} 
	if (($::g_stdin == 1) && ($add_connect_time > 600))  {
      $self->{last_update} = $::ev_unixtime;
	  $add_connect_time = 0;
	} 
	

    $self->getAddress();
	my $address       = $self->get("address");
	
	unless ($playerid)
	{
		warn ("Player->Update() with no playerid set!\n");
		return 0;
	}
	
	if (($::g_stdin == 0) && ($self->{session_start_pos} == 0)) {
      $self->{session_start_pos} = $self->getRank();
    }

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

	if ($::g_stdin == 0) {
      my $last_history_day = $self->get("last_history_day");
      if ($last_history_day ne sprintf("%02d", $mday)) {
        my $query = "
           INSERT IGNORE INTO
           hlstats_Players_History
      	   (  
			 playerId,
			 eventTime,
 			 game
 	       ) VALUES (
			 '" . $playerid . "',
			 '" . $date ."',
			 '" . $::g_servers{$srv_addr}->{game} . "'
	       )
	     ";
	    my $result = &::doQuery($query);
	    $result->finish;
	    $self->{day_skill_change} = 0;
        $self->{last_history_day} = sprintf("%02d", $mday);
	  }   
	}  
    
    my $add_history_skill = 0;
	if ($self->get("last_update_skill") > 0)  {
	  $add_history_skill = $skill - $self->get("last_update_skill");
	} 
    $self->{day_skill_change} += $add_history_skill;
    my $last_skill_change      = $self->get("day_skill_change");


	my $is_bot         = $self->get("is_bot");
    my $server_address = $self->get("server");
	if (($is_bot == 1) && ($::g_servers{$server_address}->get("ignore_bots") == 1)) {
  	  # Update player details
	  my $query = "
		UPDATE
			hlstats_Players
		SET
			connection_time = connection_time + $add_connect_time,
			lastName='" . &::quoteSQL($name) . "',
			clan=0,
			kills=kills + $kills,
			deaths=deaths + $deaths,
			suicides=suicides + $suicides,
			skill=0,
			headshots=headshots + $headshots,
			shots=shots + $shots,
			hits=hits + $hits,
			last_event=".time().",
			hideranking=1
		WHERE
			playerId='$playerid'
	  ";
	  &::doQuery($query, "Player->updateDB(): $callref");
	} else {
  	  # Update player details
	  my $query = "
		UPDATE
			hlstats_Players
		SET
			connection_time = connection_time + $add_connect_time,
			lastName='" . &::quoteSQL($name) . "',
			lastAddress='$address',
		    city='".&::quoteSQL($self->{city})."',
		    state='".&::quoteSQL($self->{state})."',
		    country='".&::quoteSQL($self->{country})."',
		    flag='".&::quoteSQL($self->{flag})."',
		    lat='$self->{lat}',
		    lng='$self->{lng}',
			clan='$clan',
			kills=kills + $kills,
			deaths=deaths + $deaths,
			suicides=suicides + $suicides,
			skill=$skill,
			headshots=headshots + $headshots,
			shots=shots + $shots,
			hits=hits + $hits,
			last_event=".time().",
			last_skill_change=$last_skill_change
		WHERE
			playerId='$playerid'
	  ";
	  &::doQuery($query, "Player->updateDB(): $callref");

   	  if ($::g_stdin == 0) {
    	# Update player details
	    my $query = "
		  UPDATE
			hlstats_Players_History
		  SET
			connection_time = connection_time + $add_connect_time,
			kills=kills + $kills,
			deaths=deaths + $deaths,
			suicides=suicides + $suicides,
			skill=$skill,
			headshots=headshots + $headshots,
			shots=shots + $shots,
			hits=hits + $hits,
			skill_change=skill_change + $add_history_skill
		  WHERE
			playerId='$playerid'
			AND eventTime='$date'
    		AND game='".$::g_servers{$srv_addr}->{game}."'
	    ";
	    &::doQuery($query, "Player->updateDB(): $callref");
	  }  

	}
	
	if ($name)
	{
		# Update alias details
		$query = "
			UPDATE
				hlstats_PlayerNames
			SET
  			    connection_time = connection_time + $add_connect_time,
				kills=kills + $kills,
				deaths=deaths + $deaths,
				suicides=suicides + $suicides,
   			    headshots=headshots + $headshots,
    			shots=shots + $shots,
   			    hits=hits + $hits"
		;
		
		unless ($leaveLastUse)
		{
			# except on ChangeName we update the last use on a player's old name
			
			$query .= ",
				lastuse=" . $::ev_datetime . ""
			;
		}
		
		$query .= "
			WHERE
				playerId='" . $playerid . "'
				AND name='" . &::quoteSQL($self->get("name")) . "'
		";
		&::doQuery($query);
	}
	
	# reset player stat properties
	$self->set("kills",     0, 1);
	$self->set("deaths",    0, 1);
	$self->set("suicides",  0, 1);
	$self->set("headshots", 0, 1);
	$self->set("shots",     0, 1);
	$self->set("hits",      0, 1);
	
	if (($is_bot == 1) && ($::g_servers{$server_address}->get("ignore_bots") == 1)) {
	  $skill        = 0;
	  $skill_change = 0;
	}
	
	if ($::g_stdin == 0) {
   	  # Update live stats
	  my $query = "
		REPLACE INTO
			hlstats_Livestats
		SET
		    player_id=$playerid,
		    server_id=$serverid,
		    cli_address='$address',
		    cli_city='".&::quoteSQL($self->{city})."',
		    cli_country='".&::quoteSQL($self->{country})."',
		    cli_flag='".&::quoteSQL($self->{flag})."',
		    cli_state='".&::quoteSQL($self->{state})."',
		    cli_lat='$self->{lat}',
		    cli_lng='$self->{lng}',
	    	steam_id='$steamid',
			name='" . &::quoteSQL($name) . "',
			team='$team',
			kills=$map_kills,
			deaths=$map_deaths,
			suicides=$map_suicides,
			headshots=$map_headshots,
			shots=$map_shots,
			hits=$map_hits,
			is_dead=$is_dead,
			has_bomb=$has_bomb,
			ping=$ping,
			connected=$connected,
			skill_change=$skill_change,
			skill=$skill
	  ";
	  &::doQuery($query);
	}

    if ($::g_stdin == 0)  {
      $self->{last_update} = time();
	} elsif ($::g_stdin == 1)  {
      $self->{last_update} = $::ev_unixtime;
	}
	
	$self->{last_update_skill} = $skill;
	
	&::printNotice("Updated player object " . $self->getInfoString());
	
	return 1;
}


#
# Update player timestamp (time of last event for player - used to detect idle
# players)
#

sub updateTimestamp
{
	my ($self, $timestamp) = @_;
	$timestamp = $::ev_unixtime
		unless ($timestamp);
	$self->{timestamp} = $timestamp;
	return $timestamp;
}



sub deleteLivestats
{
	my ($self) = @_;
	my $playerid = $self->get("playerid");

	# delete live stats
	my $query = "DELETE FROM hlstats_Livestats WHERE player_id=$playerid";
	&::doQuery($query);
}


#
# Returns a string of information about the player.
#

sub getInfoString
{
	my ($self) = @_;
	
	my $name = $self->get("name");
	my $playerid = $self->get("playerid");
	my $userid   = $self->get("userid");
	my $uniqueid = $self->get("uniqueid");
	my $team = $self->get("team");

	return "\"$name\" \<P:$playerid,U:$userid,W:$uniqueid,T:$team\>";
}


sub getAddress
{
	my ($self) = @_;
	my $p_address = $self->get("address");
	my $is_bot    = $self->get("is_bot");
	if (($::g_stdin == 0) && ($is_bot == 0) && (!$p_address))
	{
  	  &::printNotice("rcon_getaddress");
	  $server_address = $self->get("server");
	  my $result = $::g_servers{$server_address}->rcon_getaddress($self->get("plain_uniqueid"));
      if ($result) {
        $self->{address}  = $result->{Address};
        $self->{cli_port} = $result->{ClientPort};
        $self->{ping}     = $result->{Ping};
        &::printEvent("RCON", "Got Address $self->{address} for Player $self->{name}", 1);
      } 
	  &::printNotice("rcon_getaddress successfully");

  	  if ($self->{address} ne "") {
        $::g_servers{$server_address}->get_player_country($self->{playerid}, $self->{plain_uniqueid}, $self->{address});
  	  }

	}  
	
	
    return 1;
}


sub getRank
{
   my ($self, $game) = @_;
	
   my $p_steamid  = $self->get("plain_uniqueid");
   my $srv_addr  = $self->get("server");
   my $result = &::doQuery("
            SELECT
                  skill,
                  IFNULL(kills / deaths, '-') AS kpd
            FROM
                  hlstats_Players
            WHERE
                  playerId='" . $self->get("playerid") . "'
            ");
   my ($skill, $kpd) = $result->fetchrow_array;
    
   my $rank_number = 0;
   if ($skill > 0) {
      my $result = &::doQuery("
            SELECT
               COUNT(skill)
            FROM 
               hlstats_Players
            WHERE 
   		       game='".$::g_servers{$srv_addr}->{game}."'
               AND skill > '".$skill."'
               AND hideranking = 0
               AND kills >= 1
               AND IF($::g_minactivity > (UNIX_TIMESTAMP() - last_event), UNIX_TIMESTAMP() - last_event, -1) >= 0
            ");
       $ranknumber = $result->fetchrow_array;
       $ranknumber++;

       my $result = &::doQuery("
		    SELECT
   			    playerId,
    			IFNULL(kills / deaths, '-') AS kpd
	         FROM
	            hlstats_Players
		     WHERE
			    game='".$::g_servers{$srv_addr}->{game}."' 
                AND skill = '".$skill."'
                AND hideranking = 0
         		AND kills >= 1
                AND playerId <> '" . $self->get("playerid") . "'
                AND IF($::g_minactivity > (UNIX_TIMESTAMP() - last_event), UNIX_TIMESTAMP() - last_event, -1) >= 0
              HAVING
                kpd >= '".$kpd."'
	  	      ORDER BY skill DESC, kpd DESC,  lastName ASC
    	 ");
    	 
     my $break = 0; 	 
     while (my ($rowdata) = $result->fetchrow_array)  {
       if ($rowdata['playerId'] == $self->get("playerid")) {
         $break++;
       } else {  
         if ($break == 0)  {
           $ranknumber++;
        }  
       }   
     }	
   }
   if ($ranknumber > 0) {
 	  return $ranknumber;
   } else {
  	 return 0;
   }	  
}

1;