package HLstats_Server;
#
# HLstatsX - Real-time player and clan rankings and statistics for Half-Life 2
# http://www.hlstatsx.com/
# Copyright (C) 2005-2007 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 POSIX;
use IO::Socket;

sub new
{
	my ($class_name, $serverId, $address, $port, $server_name, $rcon_pass, $game, $publicaddress) = @_;
	
	my ($self) = {};
	
	bless($self, $class_name);
	
	$self->{id}             = $serverId;
	$self->{address}        = $address;
	$self->{port}           = $port;
	$self->{game}           = $game;
	$self->{rcon}           = $rcon_pass;
    $self->{rcon_obj}	    = null;
    $self->{name}           = $server_name;
    $self->{auto_ban}       = 0;
    $self->{contact}        = "";
    $self->{hlstats_url}    = "";
    $self->{publicaddress}  = $publicaddress;
	$self->{play_game}      = $game;
    
    $self->{last_heartbeat} = 0;
	$self->{last_event}     = 0;
	$self->{last_check}     = time();
    
	$self->{lines}         = 0;
	$self->{map}           = "";
	$self->{numplayers}    = 0;
	$self->{minplayers}    = 6;
	$self->{maxplayers}    = 0;
	
    $self->{players}       = 0;
 	$self->{rounds}        = 0;
	$self->{kills}         = 0;
	$self->{suicides}      = 0;
	$self->{headshots}     = 0;
	$self->{ct_shots}      = 0;
	$self->{ct_hits}       = 0;
	$self->{ts_shots}      = 0;
	$self->{ts_hits}       = 0;
	$self->{bombs_planted} = 0;
	$self->{bombs_defused} = 0;
	$self->{ct_wins}       = 0;
	$self->{ts_wins}       = 0; 
	$self->{map_started}   = time();
	$self->{map_changes}   = 0;
	$self->{map_rounds}    = 0;
	$self->{map_ct_wins}   = 0;
	$self->{map_ts_wins}   = 0; 
	$self->{map_ct_shots}  = 0;
	$self->{map_ct_hits}   = 0;
	$self->{map_ts_shots}  = 0; 
	$self->{map_ts_hits}   = 0; 

	# team balancer
	$self->{ba_enabled}       = 0;
	$self->{ba_ct_wins}       = 0;
	$self->{ba_ts_win}        = 0;
	$self->{ba_ct_frags}      = 0;
	$self->{ba_ts_frags}      = 0;
	$self->{ba_winner}        = ();
	$self->{ba_map_rounds}    = 0;
	$self->{ba_last_swap}     = 0;
	$self->{ba_player_switch} = 0;  # player switched on his own
	
	$self->{rawsocket_support}             = 0;
	$self->{rawsocket_helpnotice}          = 0;
	$self->{show_stats}                    = 0;
	$self->{broadcasting_events}           = 0;
	$self->{broadcasting_player_actions}   = 0;
	$self->{broadcasting_command}          = "";
 	$self->{broadcasting_command_steamid}  = 0;
 	$self->{broadcasting_command_announce} = "say";
	$self->{player_events}                 = 1; 
 	$self->{player_command}                = "say";
 	$self->{player_command_steamid}        = 0;
 	$self->{player_command_osd}            = "";
	$self->{player_admin_command}          = 0;
	$self->{broadcasting_serverdata}       = 0;
	$self->{broadcasting_server_address}   = "";
	$self->{broadcasting_server_port}      = 0;
	$self->{broadcasting_server_interval}  = 100;
	$self->{broadcasting_server_resolved}  = "";
	$self->{globalstats_server_address}    = "";
	$self->{globalstats_server_port}       = 0;
	$self->{globalstats_server_resolved}   = "";
	
	$self->{total_kills}                   = 0;
	$self->{total_headshots}               = 0;
	$self->{total_suicides}                = 0;
	$self->{total_rounds}                  = 0;
	$self->{total_shots}                   = 0;
	$self->{total_hits}                    = 0;

	$self->{track_server_load}             = 0;
	$self->{track_server_timestamp}        = 0;
	
	$self->{ignore_nextban}                = ();
	$self->{use_browser}                   = 0;
	$self->{round_status}                  = 0;
	$self->{min_players_rank}              = 1;
	$self->{admins}                        = ();
	$self->{ignore_bots}                   = 0;
	$self->{tk_penalty}                    = 0;
	$self->{suicide_penalty}               = 0;
	$self->{skill_mode}                    = 0;
	$self->{game_type}                     = 0;
	
	$self->{mod}                           = "";
	$self->{switch_admins}                 = 0;
	$self->{public_commands}               = 1;
	
    $self->{mani_beta_version}             = 0;
	
	if ($self->get("rcon"))
	{
	  $self->init_rcon();
#	  $self->{rcon_obj}->execute("cvarlist", 1);
	} 
	
	$self->updateDB();
	
	return $self;
}

sub is_admin
{
  my($self, $steam_id) = @_;
  for (@{$self->{admins}}) { 
    if ($_ eq $steam_id) {
      return 1;
    }   
  }  
  return 0;
}



sub get_cmd
{
  my($self, $cmd) = @_;

  if ($self->{mod} ne "")  {
    my $mod = $self->{mod};
    if ($cmd eq "browse") {
      if ($mod eq "MANI") {
        if ($self->{mani_beta_version} > 0) {
          return "ma_hlx_browse"
        }
        return "\@ma_browse";
      } elsif ($mod eq "SOURCEMOD") {
        return "hlx_sm_browse"
      } elsif ($mod eq "BEETLE") {
        return "hlx_browse";
      } elsif ($mod eq "MINISTATS") {
        return "ms_browse";
      }  
    } elsif ($cmd eq "swap") {
      if ($mod eq "MANI") {
        return "ma_swapteam";
      } elsif ($mod eq "BEETLE") {
        return "hlx_swap";
      } elsif ($mod eq "SOURCEMOD") {
        return "hlx_sm_swap";
      } elsif ($mod eq "MINISTATS") {
        return "ms_swap";
      }  
    } elsif ($cmd eq "exec") {
      if ($mod eq "MANI")  {
        return "ma_cexec";
      } elsif ($mod eq "BEETLE") {
        return "hlx_exec";
      }  
    } elsif ($cmd eq "global_chat") {
      if ($mod eq "MANI")  {
        return "ma_psay";
      } elsif ($mod eq "BEETLE") {
        return "admin_psay";
      } elsif ($mod eq "SOURCEMOD") {
        return "hlx_sm_psay";
      } elsif ($mod eq "MINISTATS") {
        return "ms_psay";
      }  
    }  
  }
  return "";
}


sub format_userid {
  my($self, $userid) = @_;

  if ($self->{mod} ne "") {
    my $mod = $self->{mod};
    if ($mod eq "MANI") {
      return "\"".$userid."\"";
    } elsif ($mod eq "SOURCEMOD") {
      return "\"".$userid."\"";
    } elsif ($mod eq "MINISTATS") {
      return "\"".$userid."\"";
    } elsif ($mod eq "BEETLE") {
      return "\"".$userid."\"";
    }  
  }  
  return "\"".$userid."\"";
}


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

sub set
{
	my ($self, $key, $value) = @_;
	
	if (defined($self->{$key}))
	{
		if ($self->get($key) eq $value)
		{
			if ($g_debug > 2)
			{
				&::printNotice("Hlstats_Server->set ignored: Value of \"$key\" is already \"$value\"");
			}
			return 0;
		}
		
		$self->{$key} = $value;
		
		if ($key eq "hlstats_url")  {
        	# so ingame browsing will work correctly
            $self->{ingame_url}  = $value;
            $self->{ingame_url}  =~ s/\/hlstats.php//;
         	&::printEvent("SERVER", "Ingame-URL: ".$self->{ingame_url}, 1);
		}
		return 1;
	}
	else
	{
		warn("HLstats_Server->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) = @_;
	
	$amount = int($amount);
	$amount = 1 if ($amount == 0);
	
	my $value = $self->get($key);
	$self->set($key, $value + $amount);
}


#
# Get value of property 'key'
#

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


sub init_rcon
{
	my ($self)      = @_;
    my $server_ip   = $self->get("address");
    my $server_port = $self->get("port");
    my $rcon_pass   = $self->get("rcon");
    my $game        = $self->get("game");
	if ($rcon_pass)
	{
      $self->{rcon_obj} = new TRcon($self);
	}  
   	if ($self->{rcon_obj}) 
	{
      my $rcon_obj = $self->get("rcon_obj");
      &::printEvent ("TRCON", "Connecting to rcon on $server_ip:$server_port ... ok");
	  $self->{play_game} = $rcon_obj->getServerGame();

	  my $ma_version = $rcon_obj->getManiVersion();
	  if ($ma_version =~ "V1.2") {
    	$self->{mani_beta_version} = 1;
        &::printEvent("SERVER", "Server running manimod version: ".$ma_version, 1);
      }

	  if ($self->{play_game} eq "")  {
        $self->{play_game} = $self->{game};
	  }
	  &::printEvent("SERVER", "Server running game: ".$self->{play_game}, 1);
	  &::printEvent("SERVER", "Server running map: ".$self->get_map(), 1);
      if ($::g_mode eq "LAN")  {
        $self->get_lan_players();
      }
	}
}

sub dorcon
{
	my ($self, $command)      = @_;
    my $result;
    my $rcon_obj = $self->get("rcon_obj");
	if (($::g_stdin == 0) && ($rcon_obj) && ($self->{rcon} ne "")) {
	 
	  # replace ; to avoid executing multiple rcon commands.
      $command  =~ s/;//g;
	
      &::printNotice("RCON", $command, 1);
      $result = $rcon_obj->execute($command);
    } else {
      &::printNotice("Rcon error: No Object available");
    }
    return $result;
}	

sub rcon_getaddress
{
	my ($self, $uniqueid) = @_;
    my $result;
    my $rcon_obj = $self->get("rcon_obj");
	if (($::g_stdin == 0) && ($rcon_obj) && ($self->{rcon} ne ""))
	{
	  $result = $rcon_obj->getPlayer($uniqueid);
    } else {
      &::printNotice("Rcon error: No Object available");
    }
    return $result;
}

sub rcon_getStatus
{
	my ($self) = @_;
    my $rcon_obj = $self->get("rcon_obj");
    my $map_result;
    my $max_player_result = -1;
	if (($::g_stdin == 0) && ($rcon_obj) && ($self->{rcon} ne "")) {
	  ($map_result, $max_player_result)    = $rcon_obj->getServerData();
      ($visible_maxplayers)                = $rcon_obj->getVisiblePlayers();
      if (($visible_maxplayers != -1) && ($visible_maxplayers < $max_player_result)) {
        $max_player_result = $visible_maxplayers;
      }
    } else {
      &::printNotice("Rcon error: No Object available");
    }
    return ($map_result, $max_player_result);
}

sub rcon_getplayers
{
	my ($self) = @_;
    my %result;
    my $rcon_obj = $self->get("rcon_obj");
	if (($::g_stdin == 0) && ($rcon_obj) && ($self->{rcon} ne ""))
	{
	  %result = $rcon_obj->getPlayers();
    } else {
      &::printNotice("Rcon error: No Object available");
    }
    return %result;
}

sub track_server_load
{
  my ($self) = @_;

  if (($::g_stdin == 0) && ($self->get("track_server_load") > 0))
  {
    my $last_timestamp = $self->get("track_server_timestamp");
    my $new_timestamp  = time();
    if ($last_timestamp > 0)
    {
      if ($last_timestamp+299 < $new_timestamp)
      {
        my $server_id    = $self->get("id");
        my $act_players  = $self->get("numplayers");
        my $min_players  = $self->get("minplayers");  
        my $max_players  = $self->get("maxplayers");
        if ($max_players > 0) {
          if ($act_players > $max_players)  {
            $act_players = $max_players;
          }
        }
   	    &::doQuery("INSERT IGNORE INTO hlstats_server_load SET server_id='".$server_id."', timestamp='".$new_timestamp."', act_players='".$act_players."', min_players='".$min_players."', max_players='".$max_players."'");
	    $self->set("track_server_timestamp", $new_timestamp);
        &::printEvent("SERVER", "Insert new server load timestamp", 1);
	  }   
	} else {
      $self->set("track_server_timestamp", $new_timestamp);
	}  
  
  }
   

}

sub dostats 
{
	my ($self) = @_;
    my $rcon_obj = $self->get("rcon_obj");

	if (($::g_stdin == 0) && ($rcon_obj) && ($self->{rcon} ne ""))  
	{
      if ($self->get("broadcasting_events") == 1)
      {
        my $hpk = sprintf("%.0f", 0);
        if ($self->{total_kills} > 0) {
          $hpk = sprintf("%.2f", (100/$self->{total_kills})*$self->{total_headshots});
        }  
   	    if ($self->get("broadcasting_command_announce") ne "") {
 	      $self->dorcon($self->get("broadcasting_command_announce")." Tracking ".&::number_format($self->{players})." players with ".&::number_format($self->{total_kills})." kills and ".&::number_format($self->{total_headshots})." headshots ($hpk%)");
   	    } else {
   	      if ($self->get("broadcasting_command_steamid") == 1) {
            foreach $player (values(::g_players))	{
              if (($player->{server} eq $self->{address}.":".$self->{port}) && ($player->{is_bot} == 0))  {
                 my $p_userid  = $player->get("userid");
                 $self->dorcon($self->get("broadcasting_command")." \"$p_userid\" Tracking ".&::number_format($self->{players})." players with ".&::number_format($self->{total_kills})." kills and ".&::number_format($self->{total_headshots})." headshots ($hpk%)");
              }
            }  
          } else {
 		    $self->dorcon($self->get("broadcasting_command")." Tracking ".&::number_format($self->{players})." players with ".&::number_format($self->{total_kills})." kills and ".&::number_format($self->{total_headshots})." headshots ($hpk%)");
	  	  }  
	  	}  
      }  
    }  
}	  

sub get_map
{
  my ($self) = @_;
  if ($::g_stdin == 0) {
    if (((time() - $self->{last_check})>600) || (($self->{map} eq "") || ($self->{maxplayers} == 0))) {
      &::printNotice("get_rcon_status");
      my $temp_map        = "";
      my $temp_maxplayers = -1;
      ($temp_map, $temp_maxplayers) = $self->rcon_getStatus();
      if (($temp_map != -1) && ($temp_map ne "")) {
        $self->{map}        = $temp_map;
        $self->{last_check} = time();
      }  
      if (($temp_maxplayers != -1) && ($temp_maxplayers > 0) && ($temp_maxplayers ne "")) {
        $self->{maxplayers} = $temp_maxplayers;
        $self->update_max_players();
        $self->{last_check} = time();
      }  
      &::printNotice("get_rcon_status successfully");
    }
  }  
  return $self->{map};
}

sub update_max_players  
{
  my ($self) = @_;
  if ($::g_stdin == 0) {
    &::printNotice("update_max_server_players");
    &::printEvent("SERVER", "Update Max Players", 1);
    my $serverid   = $self->get("id");
    my $maxplayers = $self->get("maxplayers");
    if ($maxplayers > 0) {
      my $query = "
	    UPDATE
		   hlstats_Servers
    	  SET
	       max_players=$maxplayers 
	   WHERE
	        serverId='$serverid'
      ";
      &::doQuery($query);
    }   
  }  
  
}

sub update_players
{
  my ($self) = @_;

  &::printNotice("update_players");
  &::printEvent("RCON", "Update Players", 1);
  my %players = $self->rcon_getplayers();
  while ( my($pl, $player) = each(::g_players) )
  {	 
    my $pl_id = $player->get("plain_uniqueid");
    if (defined($players{$pl_id})) 
    {
      my $ping = $players{$pl_id}->{"Ping"};
      $player->set("ping", $ping);
    }
  }
}  


sub update_players_pings
{
  my ($self) = @_;

  if ($self->{numplayers} < $self->{minplayers}) 
  {
    &::printNotice("(IGNORED) NOTMINPLAYERS: Update_player_pings");
  } 
  else 
  {
    &::printNotice("update_player_pings");
    &::printEvent("RCON", "Update Player pings", 1);
    my %players = $self->rcon_getplayers();
    while ( my($pl, $player) = each(::g_players) )
    {	 
       my $pl_id = $player->get("plain_uniqueid");
       if (defined($players{$pl_id})) 
       {
         if ($player->{is_bot} == 0)  {
           my $ping = $players{$pl_id}->{"Ping"};
           $player->set("ping", $ping);
           &::recordEvent(
                          "Latency", 0,
                          $player->get("playerid"),
                          $ping
                         );
         }                
       }
    }
    &::printNotice("update_player_pings successfully");
  }  
}

sub get_lan_players
{
  my ($self) = @_;

  if ($::g_mode eq "LAN")  {
    &::printNotice("get_lan_players");
    &::printEvent("RCON", "Get LAN players", 1);
    my %players = $self->rcon_getplayers();
    while ( my($p_uid, $p_obj) = each(%players) )
    {	
      my $srv_addr = $self->get("address").":".$self->get("port");
      my $userid   = $p_obj->{"UserID"};
      my $name     = $p_obj->{"Name"};
      my $address  = $p_obj->{"Address"};
      ::g_lan_noplayerinfo->{"$srv_addr/$userid/$name"} = {
					            ipaddress => $address,
						        userid => $userid,
						        name => $name,
						        server => $srv_addr
 			                 };
    }
    &::printNotice("get_lan_players successfully");
  }  
}


sub send_heartbeat
{
	my ($self)  = @_;
	my $q_version = "1.01";

   	if (($::g_stdin == 0) && ($self->get("broadcasting_serverdata") > 0))
   	{
	    my $host = $self->get("broadcasting_server_address");
		if ($self->get("broadcasting_server_resolved") eq "") {
	  	  if ($host !~ /[0-9]+[.][0-9]+[.][0-9]+[.][0-9]+/) { 
	        $host_data = (gethostbyname($host))[4];
	        $self->set("broadcasting_server_resolved", join(".", unpack("C4", $host_data)));
	      } else {
	        $self->set("broadcasting_server_resolved", $host);
	      }  
	    }  
	    my $address       = $self->{broadcasting_server_resolved};
		my $port          = $self->get("broadcasting_server_port");
	    my $dest          = sockaddr_in($port, inet_aton($address));

  		my $s_address     = $self->get("address");
		my $s_port        = $self->get("port");
	    if ($self->{publicaddress} ne "") {
		  ($s_address, $s_port) = split(/:/, $self->{publicaddress});
		}  
		
		my $s_name        = $self->get("name");
		my $s_contact     = $self->get("contact");
		my $s_url         = $self->get("hlstats_url");
		my $s_version     = $::g_version;
		my $s_id          = $self->get("id");
		my $s_auto_ban    = $self->get("auto_ban");
		my $s_team_bal    = $self->get("ba_enabled");
		
		my $s_act_players = $self->get("numplayers");
		my $s_max_players = $self->get("maxplayers");
		my $s_min_players = $self->get("minplayers");
		my $s_game        = $self->get("game");
		if ($g_servers{$s_addr}->{play_game} ne "") {
		  $s_game = $g_servers{$s_addr}->{play_game};
		}  
		my $s_rounds      = $self->get("total_rounds");
		my $s_suicides    = $self->get("total_suicides");
		my $s_map         = $self->get("map");
		my $s_map_time    = $self->get("map_started");
		my $s_players     = $self->get("players");
		my $s_kills       = $self->get("total_kills");
		my $s_headshots   = $self->get("total_headshots");
		my $s_shots       = $self->get("total_shots");
		my $s_hits        = $self->get("total_hits");
		my $s_min_rank    = $self->get("min_players_rank");
		
		my $s_skill_mode  = $self->get("skill_mode");
        my $s_ignore_bots = $self->get("ignore_bots");
		
	    $msg = $s_name.chr(0xff).$s_address.chr(0xff).$s_port.chr(0xff).$s_contact.chr(0xff).$s_url.chr(0xff).$s_version.chr(0xff).
	           $s_id.chr(0xff).$s_act_players.chr(0xff).$s_max_players.chr(0xff).$s_min_players.chr(0xff).$s_auto_ban.chr(0xff).
	           $s_team_bal.chr(0xFF).$s_game.chr(0xff).$s_min_rank.chr(0xff).$s_rounds.chr(0xff).$s_suicides.chr(0xff).$s_map.chr(0xff).$s_map_time.chr(0xff).
	           $s_players.chr(0xff).$s_kills.chr(0xff).$s_headshots.chr(0xff).$s_shots.chr(0xff).$s_hits.chr(0xff).$s_skill_mode.chr(0xff).$s_ignore_bots.chr(0xff);
	    $msg = chr(0xff).chr(0xff)."S".chr(0xff).$q_version.chr(0xff).$msg;
	   	$bytes = send($::s_socket, $msg, 0, $dest);
	   	&::printEvent("MASTER", "Sent $bytes bytes to master server at '$address:$port' [Serverdata]", 1);
	   	
	   	
	   	if ($self->get("broadcasting_serverdata") > 2)
	   	{
	      $player_msg  = "";
	      $i = 0;
	      while (my($pl, $player) = each(::g_players) )
	      {
	        if ($self->get("id") == $player->{server_id}) 
	        {
			  my $p_id            = $player->get("playerid");        
			  my $p_address       = $player->get("address");        
			  my $p_steamid       = $player->get("plain_uniqueid");        
		      my $p_name          = $player->get("name");
	          my $p_team          = $player->get("team");
	          my $p_kills         = $player->get("map_kills");
			  my $p_deaths        = $player->get("map_deaths");
			  my $p_suicides      = $player->get("map_suicides");
			  my $p_headshots     = $player->get("map_headshots");
			  my $p_shots         = $player->get("map_shots");
			  my $p_hits          = $player->get("map_hits");
	          my $p_ping          = $player->get("ping");
	 	      my $p_connected     = $player->get("connect_time");
		      my $p_skill_change  = $player->get("session_skill");
		      my $p_skill         = $player->get("skill");
		      
		      my $p_country_code  = $player->get("flag"); 
		      my $p_country_name  = $player->get("country"); 
		      my $p_city          = $player->get("city"); 
		      my $p_state         = $player->get("state"); 
		      my $p_lat           = $player->get("lat"); 
		      my $p_lng           = $player->get("lng"); 
		      
	          $player_msg .= $p_country_code.chr(0xfe).$p_country_name.chr(0xfe).$p_city.chr(0xfe).$p_state.chr(0xfe).$p_lat.chr(0xfe).$p_lng.chr(0xfe).
	                         $p_id.chr(0xfe).$p_steamid.chr(0xfe).$p_name.chr(0xfe).$p_team.chr(0xfe).$p_kills.chr(0xfe).
	                         $p_deaths.chr(0xfe).$p_suicides.chr(0xfe).$p_headshots.chr(0xfe).$p_shots.chr(0xfe).
	                         $p_hits.chr(0xfe).$p_ping.chr(0xfe).$p_connected.chr(0xfe).$p_skill_change.chr(0xfe).$p_skill.chr(0xff);
	          $i++;               
	          if ($i % 5 == 0)
	          {
			    $player_msg = chr(0xff).chr(0xff)."P".chr(0xff).$q_version.chr(0xff).$s_address.chr(0xff).$s_port.chr(0xff).$player_msg;
	      	    $bytes = send($::s_socket, $player_msg, 0, $dest);
		        &::printEvent("MASTER", "Sent $bytes bytes to master server at '$address:$port' [Playerdata]", 1);
		        $player_msg = "";
	          }
	        }
	      }  
	   	  if ($player_msg ne "")
	   	  {
	        $player_msg = chr(0xff).chr(0xff)."P".chr(0xff).$q_version.chr(0xff).$s_address.chr(0xff).$s_port.chr(0xff).$player_msg;
	 	    $bytes = send($::s_socket, $player_msg, 0, $dest);
		    &::printEvent("MASTER", "Sent $bytes bytes to master server at '$address:$port' [Playerdata]", 1);
	  	  }  
	   	}
	
	   	$self->set("last_heartbeat", time());
   	}
   	
}

sub send_cheater
{
	my ($self, $steam_id, $last_name)  = @_;
	my $q_version = "1.01";

   	if (($::g_stdin == 0) && ($self->get("broadcasting_serverdata") > 0))
   	{
	    my $host = $self->get("broadcasting_server_address");
		if ($self->get("broadcasting_server_resolved") eq "") {
	  	  if ($host !~ /[0-9]+[.][0-9]+[.][0-9]+[.][0-9]+/) { 
	        $host_data = (gethostbyname($host))[4];
	        $self->set("broadcasting_server_resolved", join(".", unpack("C4", $host_data)));
	      } else {
	        $self->set("broadcasting_server_resolved", $host);
	      }  
	    }  

	    my $address        = $self->{broadcasting_server_resolved};
		my $port           = $self->get("broadcasting_server_port");
	    my $dest           = sockaddr_in($port, inet_aton($address));
	    
	    if ($last_name eq "") {
	      $last_name = "_unknown_";
	    }  

	    $msg = $steam_id.chr(0xff).$last_name.chr(0xff);
	    $msg = chr(0xff).chr(0xff)."C".chr(0xff).$q_version.chr(0xff).$msg;
	   	$bytes = send($::s_socket, $msg, 0, $dest);
	   	&::printEvent("MASTER", "Sent $bytes bytes to master server at '$address:$port' [Cheater]", 1);
	 }  	
}

sub send_global_stats
{
	my ($self, $prefix, $message, $description)  = @_;
	my $q_version = "1.01";

   	if (($::g_stdin == 0) && ($self->get("broadcasting_serverdata") == 7))
   	{
	    my $host = $self->get("globalstats_server_address");
		if ($self->get("globalstats_server_resolved") eq "") {
	  	  if ($host !~ /[0-9]+[.][0-9]+[.][0-9]+[.][0-9]+/) { 
	        $host_data = (gethostbyname($host))[4];
	        $self->set("globalstats_server_resolved", join(".", unpack("C4", $host_data)));
	      } else {
	        $self->set("globalstats_server_resolved", $host);
	      }  
	    }  

	    my $address        = $self->{globalstats_server_resolved};
		my $port           = $self->get("globalstats_server_port");
	    my $dest           = sockaddr_in($port, inet_aton($address));
	    
  		my $s_address     = $self->get("address");
		my $s_port        = $self->get("port");
	    if ($self->{publicaddress} ne "") {
		  ($s_address, $s_port) = split(/:/, $self->{publicaddress});
		}  
	    
	    my $msg = chr(0xff).chr(0xff).$prefix.chr(0xff).$q_version.chr(0xff).$s_address.':'.$s_port.chr(0xff).$message;
	   	$bytes = send($::s_socket, $msg, 0, $dest);
	   	&::printEvent("GLOBALSTATS", "Sent $bytes bytes to global stats server at '$address:$port' [$description]", 1);
	 }  	
}

sub get_player_country
{
	my ($self, $playerid, $player_unique_id, $cli_address)  = @_;
	my $q_version = "1.01";

   	if (($::g_stdin == 0) && ($self->get("broadcasting_serverdata") > 0))
   	{
	    my $host = $self->get("broadcasting_server_address");
		if ($self->get("broadcasting_server_resolved") eq "") {
	  	  if ($host !~ /[0-9]+[.][0-9]+[.][0-9]+[.][0-9]+/) { 
	        $host_data = (gethostbyname($host))[4];
	        $self->set("broadcasting_server_resolved", join(".", unpack("C4", $host_data)));
	      } else {
	        $self->set("broadcasting_server_resolved", $host);
	      }  
	    }  

	    my $address        = $self->{broadcasting_server_resolved};
		my $port           = $self->get("broadcasting_server_port");
	    my $dest           = sockaddr_in($port, inet_aton($address));

	    $msg = $playerid.chr(0xfe).$player_unique_id.chr(0xfe).$cli_address.chr(0xff);
	    $msg = chr(0xff).chr(0xff)."A".chr(0xff).$q_version.chr(0xff).$msg;
	   	$bytes = send($::s_socket, $msg, 0, $dest);
	   	&::printEvent("MASTER", "Sent $bytes bytes to master server at '$address:$port' [Country]", 1);
	   	
	 }  	
}




sub send_udp_data
{
	my ($self, $msg, $address, $port) = @_;
	
	if (($::g_stdin == 0) && ($self->get("rawsocket_support") == 1))
	{
  	  my $s_address = $self->get("address");
      my $s_port    = $self->get("port");
      my $dest      = sockaddr_in(27510, inet_aton($s_address));
      $msg = chr(0xff).chr(0xff)."R".chr(0xff).$s_address.chr(0xff).$s_port.chr(0xff).$address.chr(0xff).$port.chr(0xff).$msg.chr(0xff);
 	  $bytes = send($::s_socket, $msg, 0, $dest);
	  &::printEvent("SENDPROXY", "Sent $bytes bytes to proxy server at '$s_address:$s_port'", 1);
    }	
}


sub clear_winner
{
  my ($self) = @_;
  &::printNotice("clear_winner");
  @{$self->{winner}} = ();
}

sub add_round_winner
{
  my ($self, $team) = @_;
  
  &::printNotice("add_round_winner");
  $self->{winner}[($self->{map_rounds} % 7)] = $team;
  $self->increment("ba_map_rounds");
  $self->increment("map_rounds");
  $self->increment("rounds");
  $self->increment("total_rounds");
  
  $self->{ba_ct_wins} = 0;
  $self->{ba_ts_wins} = 0;
  
  for (@{$self->{winner}}) 
  {
    if ($_ eq "ct")
    {
      $self->increment("ba_ct_wins");
    } elsif ($_ eq "ts")
    {
      $self->increment("ba_ts_wins");
    }
  }
}

sub switch_player
{
  my ($self, $playerid, $is_bot) = @_;
  $self->dorcon($self->get_cmd("swap")." \"$playerid\"");
  if ($self->get("player_events") == 1)  
  {
    if (($is_bot == 0) && ($self->get("player_command_steamid") == 1)) {
      $cmd_str = $self->get("player_command")." \"$playerid\" You were switched to balance teams";
    } else {
      $cmd_str = $self->get("player_command")." You were switched to balance teams";
    }
	$self->dorcon($cmd_str);
  }	
}


sub analyze_teams
{
  my ($self) = @_;
  
  if (($::g_stdin == 0) && ($self->{numplayers} < $self->{minplayers})) 
  {
    &::printNotice("(IGNORED) NOTMINPLAYERS: analyze_teams");
  } 
  elsif (($::g_stdin == 0) && ($self->{ba_enabled} > 0))
  {
    &::printNotice("analyze_teams");
    my $ts_skill     = 0;
    my $ts_avg_skill = 0;
    my $ts_count     = 0;
    my $ts_wins      = $self->{ba_ts_wins};
    my $ts_kills     = 0;
    my $ts_deaths    = 0;
    my $ts_diff      = 0;
    my @ts_players   = ();

    my $ct_skill     = 0;
    my $ct_avg_skill = 0;
    my $ct_count     = 0;
    my $ct_wins      = $self->{ba_ct_wins};
    my $ct_kills     = 0;
    my $ct_deaths    = 0;
    my $ct_diff      = 0;
    my @ct_players   = ();
    
    my $server_id   = $self->{id};
    while ( my($pl, $player) = each(::g_players) )
    {	
      my @Player      = ( $player->{server_id},  $player->{name},       $player->{plain_uniqueid}, $player->{skill},   
                          $player->{team},       $player->{map_kills},  $player->{map_deaths},     ($player->{map_kills}-$player->{map_deaths}),
                          $player->{is_dead},    $player->{userid},     $player->{is_bot});
    
      if ($Player[0] == $server_id)
      {
        if ($Player[4] eq "TERRORIST")
        {
          push(@{$ts_players[$ts_count]}, @Player);
          $ts_skill   += $Player[3]; 
          $ts_count   += 1;
          $ts_kills   += $Player[5];
          $ts_deaths  += $Player[6];
        } elsif ($Player[4] eq "CT")
        {
          push(@{$ct_players[$ct_count]}, @Player);
          $ct_skill   += $Player[3]; 
          $ct_count   += 1;
          $ct_kills   += $Player[5]; 
          $ct_deaths  += $Player[6]; 

        }
      }
    }
    @ct_players = sort { $b->[7] <=> $a->[7]} @ct_players;
    @ts_players = sort { $b->[7] <=> $a->[7]} @ts_players;
    
    &::printEvent("TEAM", "Checking Teams", 1);
    $admin_msg = "AUTO-TEAM BALANCER: CT ($ct_count) $ct_kills:$ct_deaths  [$ct_wins - $ts_wins] $ts_kills:$ts_deaths ($ts_count) TS";
    if ($self->get("player_events") == 1)  
    {
	  if ($self->get("player_admin_command") ne "") {
        $cmd_str = $self->get("player_admin_command")." $admin_msg";
   	    $self->dorcon($cmd_str);
   	  }  
    }	

    $rcon_msg ="ATB - Checking Teams";
    if ($self->get("broadcasting_events") == 1)
	{
      if ($self->get("broadcasting_command_steamid") == 1) {
        foreach $player (values(::g_players))	{
          if (($player->{server} eq $self->get("address").":".$self->get("port")) && ($player->{is_bot} == 0))  {
            my $p_userid  = $player->get("userid");
            $self->dorcon($self->get("broadcasting_command")." \"$p_userid\" ".$rcon_msg);
          }
        }  
      } else {
 	    $self->dorcon($self->get("broadcasting_command")." ".$rcon_msg);
	  }  
	}  

    if ($self->{ba_map_rounds} >= 2)    # need all players for numerical balacing, at least 2 for getting all players
    {
      my $action_done = 0;
      if ($self->{ba_last_swap} > 0)
      {
        $self->{ba_last_swap}--;
      }
      
      if ($ct_count + 1 < $ts_count)         # ct need players
      {
        $needed_players = floor( ($ts_count - $ct_count) / 2);
        if ($ct_wins < 2)
        {
          @ts_players = sort { $b->[7] <=> $a->[7]} @ts_players;
        } 
        else
        {
          @ts_players = sort { $a->[7] <=> $b->[7]} @ts_players;
        }  
        foreach my $entry (@ts_players) 
        {
          if ($needed_players > 0) # how many we need to make teams even (only numerical)
          {
            if (@{$entry}[8] == 1)  # only dead players!!
            {
              if (($self->{switch_admins} == 1) || (($self->{switch_admins} == 0) && ($self->is_admin(@{$entry}[2]) == 0)))  {
                $self->switch_player(@{$entry}[9], @{$entry}[10]); 
                $action_done++;
                $needed_players--;
              }  
            }
          }  
        }
      } 
      elsif  ($ts_count + 1 < $ct_count)  # ts need players
      {
        $needed_players = floor( ($ct_count - $ts_count) / 2);
        if ($ts_wins < 2)
        {
          @ct_players = sort { $b->[7] <=> $a->[7]} @ct_players;  # best player
        } 
        else
        {
          @ct_players = sort { $a->[7] <=> $b->[7]} @ct_players;  # worst player
        }  
        foreach my $entry (@ct_players) 
        {
          if ($needed_players > 0) # how many we need to make teams even (only numerical)
          {
            if (@{$entry}[8] == 1)  # only dead players!!
            {
              if (($self->{switch_admins} == 1) || (($self->{switch_admins} == 0) && ($self->is_admin(@{$entry}[2]) == 0)))  {
                $self->switch_player(@{$entry}[9], @{$entry}[10]); 
                $action_done++;
                $needed_players--;
              }  
            }
          }  
        }
      }
      
      if (($action_done == 0) && ($self->{ba_last_swap} == 0) && ($self->{ba_map_rounds} >= 7) && ($self->{ba_player_switch} == 0)) # frags balancing (last swap 3 rounds before)
      {
         if ($ct_wins < 2)
         {
           if ($ct_count < $ts_count)     # one player less we dont need swap just bring one over
           {
             my $ts_found = 0;
             @ts_players = sort { $b->[7] <=> $a->[7]} @ts_players;  # best player
             foreach my $entry (@ts_players) 
             {
               if ($ts_found == 0)
               {
                 if (@{$entry}[8] == 1)  # only dead players!!
                 {
                   if (($self->{switch_admins} == 1) || (($self->{switch_admins} == 0) && ($self->is_admin(@{$entry}[2]) == 0)))  {
                     $self->{ba_last_swap} = 3;
                     $self->switch_player(@{$entry}[9], @{$entry}[10]); 
                     $ts_found++;
                   }  
                 }
               }  
             }  
           }
           else                  # need to swap to players
           {
             my $ts_playerid = 0;
             my $ts_name     = "";
             my $ts_kills    = 0;
             my $ts_deaths   = 0;
             my $ct_playerid = 0;
             my $ct_name     = "";
             my $ct_kills    = 0;
             my $ct_deaths   = 0;
             
             
             my $ts_found = 0;
             @ts_players = sort { $b->[7] <=> $a->[7]} @ts_players;  # best player
             foreach my $entry (@ts_players) 
             {
               if ($ts_found == 0)
               {
                 if (@{$entry}[8] == 1)  # only dead players!!
                 {
                   if (($self->{switch_admins} == 1) || (($self->{switch_admins} == 0) && ($self->is_admin(@{$entry}[2]) == 0)))  {
                     $ts_playerid = @{$entry}[9];
                     $ts_is_bot   = @{$entry}[10];
                     $ts_name     = @{$entry}[1];
                     $ts_found++;
                   }  
                 }
               }  
             }  

             my $ct_found = 0;
             @ct_players = sort { $a->[7] <=> $b->[7]} @ct_players;  # worst player
             foreach my $entry (@ct_players) 
             {
               if ($ct_found == 0)
               {
                 if (@{$entry}[8] == 1)  # only dead players!!
                 {
                   if (($self->{switch_admins} == 1) || (($self->{switch_admins} == 0) && ($self->is_admin(@{$entry}[2]) == 0)))  {
                     $ct_playerid = @{$entry}[9];
                     $ct_is_bot   = @{$entry}[10];
                     $ct_name     = @{$entry}[1];
                     $ct_found++;
                   }  
                 }
               }  
             }  
             if (($ts_found>0) && ($ct_found>0))
             {
               $self->{ba_last_swap} = 3;
               $self->switch_player($ts_playerid, $ts_is_bot); 
               $self->switch_player($ct_playerid, $ct_is_bot); 
             }
           }
         }
         elsif ($ts_wins < 2)
         {
           if ($ts_count < $ct_count)     # one player less we dont need swap just bring one over
           {
             my $ct_found = 0;
             @ct_players = sort { $b->[7] <=> $a->[7]} @ct_players;  # best player
             foreach my $entry (@ct_players) 
             {
               if ($ct_found == 0)
               {
                 if (@{$entry}[8] == 1)  # only dead players!!
                 {
                   if (($self->{switch_admins} == 1) || (($self->{switch_admins} == 0) && ($self->is_admin(@{$entry}[2]) == 0)))  {
                     $self->{ba_last_swap} = 3;
                     $self->switch_player(@{$entry}[9], @{$entry}[10]); 
                     $ct_found++;
                   }  
                 }
               }  
             }  
           }
           else                  # need to swap to players
           {
             my $ts_playerid  = 0;
             my $ts_name      = "";
             my $ct_playerid  = 0;
             my $ct_name      = "";
             
             my $ct_found = 0;
             @ct_players = sort { $b->[7] <=> $a->[7]} @ct_players;  # best player
             foreach my $entry (@ct_players) 
             {
               if ($ct_found == 0)
               {
                 if (@{$entry}[8] == 1)  # only dead players!!
                 {
                   if (($self->{switch_admins} == 1) || (($self->{switch_admins} == 0) && ($self->is_admin(@{$entry}[2]) == 0)))  {
                     $ct_playerid = @{$entry}[9];
                     $ct_is_bot   = @{$entry}[10];
                     $ct_name     = @{$entry}[1];
                     $ct_found++;
                   }  
                 }
               }  
             }  

             my $ts_found = 0;
             @ts_players = sort { $a->[7] <=> $b->[7]} @ts_players;  # worst player
             foreach my $entry (@ts_players) 
             {
               if ($ts_found == 0)
               {
                 if (@{$entry}[8] == 1)  # only dead players!!
                 {
                   if (($self->{switch_admins} == 1) || (($self->{switch_admins} == 0) && ($self->is_admin(@{$entry}[2]) == 0)))  {
                     $ts_playerid = @{$entry}[9];
                     $ts_is_bot   = @{$entry}[10];
                     $ts_name     = @{$entry}[1];
                     $ts_found++;
                   }  
                 }
               }  
             }  
             if (($ts_found > 0) && ($ct_found > 0))
             {
               $self->{ba_last_swap} = 3;
               $self->switch_player($ts_playerid, $ts_is_bot); 
               $self->switch_player($ct_playerid, $ct_is_bot); 
             }
           }
         }
      }
    } # end if rounds > 1  
  }
}

#
# Update player information in database
#

sub updateDB
{
	my ($self) = @_;

    $self->get_map();
	
	my $serverid      = $self->get("id");
	my $rounds        = $self->get("rounds");
	my $kills         = $self->get("kills");
	my $suicides      = $self->get("suicides");
	my $headshots     = $self->get("headshots");
	my $ct_shots      = $self->get("ct_shots");
	my $ct_hits       = $self->get("ct_hits");
	my $ts_shots      = $self->get("ts_shots");
	my $ts_hits       = $self->get("ts_hits");
	my $bombs_planted = $self->get("bombs_planted");
	my $bombs_defused = $self->get("bombs_defused");
	my $ct_wins       = $self->get("ct_wins");
	my $ts_wins       = $self->get("ts_wins");
	my $act_players   = $self->get("numplayers");
	my $act_map       = $self->get("map");
	my $map_rounds    = $self->get("map_rounds");
	my $map_ct_wins   = $self->get("map_ct_wins");
	my $map_ts_wins   = $self->get("map_ts_wins");
	my $map_started   = $self->get("map_started");
	my $map_changes   = $self->get("map_changes");
	my $map_ct_shots  = $self->get("map_ct_shots");
	my $map_ct_hits   = $self->get("map_ct_hits");
	my $map_ts_shots  = $self->get("map_ts_shots");
	my $map_ts_hits   = $self->get("map_ts_hits");
	

    if ($self->{total_kills} == 0)
    {
   	  my $result = &::doQuery("
		  SELECT kills, headshots, suicides, rounds, ct_shots+ts_shots as shots, ct_hits+ts_hits as hits
		  FROM hlstats_Servers
 	      WHERE	serverId='$serverid'
	  ");
  	  ($self->{total_kills}, $self->{total_headshots}, $self->{total_suicides},$self->{total_rounds},$self->{total_shots},$self->{total_hits}) = $result->fetchrow_array();
	}   

	my $result = &::doQuery("
		SELECT count(*) as players
		FROM hlstats_Players
		WHERE game='".$self->get("game")."'
	");
	$self->{players} = $result->fetchrow_array();
	my $players = $self->get("players");
	
	
	# Update player details
	my $query = "
		UPDATE
			hlstats_Servers
		SET  
		    rounds=rounds + $rounds,
			kills=kills + $kills,
			suicides=suicides + $suicides,
			headshots=headshots + $headshots,
			bombs_planted=bombs_planted + $bombs_planted,
			bombs_defused=bombs_defused + $bombs_defused,
			players=$players,
			ct_wins=ct_wins + $ct_wins,
			ts_wins=ts_wins + $ts_wins,
			act_players=$act_players,
			act_map='$act_map',
			map_rounds=$map_rounds,
			map_ct_wins=$map_ct_wins,
			map_ts_wins=$map_ts_wins,
			map_started=$map_started,
			map_changes=map_changes+$map_changes,
			ct_shots=ct_shots + $ct_shots,
			ct_hits=ct_hits + $ct_hits,
			ts_shots=ts_shots + $ts_shots,
			ts_hits=ts_hits + $ts_hits,
			map_ct_shots=$map_ct_shots,
			map_ct_hits=$map_ct_hits,
			map_ts_shots=$map_ts_shots,
			map_ts_hits=$map_ts_hits
		WHERE
			serverId='$serverid'
	";
	&::doQuery($query);

	$self->set("rounds", 0);
	$self->set("kills", 0);
	$self->set("suicides", 0);
	$self->set("headshots", 0);
	$self->set("bombs_planted", 0);
	$self->set("bombs_defused", 0);
	$self->set("ct_wins", 0);
	$self->set("ts_wins", 0);
	$self->set("ct_shots", 0);
	$self->set("ct_hits", 0);
	$self->set("ts_shots", 0);
	$self->set("ts_hits", 0);
	$self->set("map_changes", 0);
}

1;