hlstatsx/scripts/HLstats_Server.pm

1415 lines
36 KiB
Perl

package HLstats_Server;
# HLstatsX Community Edition - Real-time player and clan rankings and statistics
# Copyleft (L) 2008-20XX Nicholas Hastings (nshastings@gmail.com)
# http://www.hlxcommunity.com
#
# HLstatsX Community Edition is a continuation of
# ELstatsNEO - Real-time player and clan rankings and statistics
# Copyleft (L) 2008-20XX Malte Bayer (steam@neo-soft.org)
# http://ovrsized.neo-soft.org/
#
# ELstatsNEO is an very improved & enhanced - so called Ultra-Humongus Edition of HLstatsX
# 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.
#
# For support and installation notes visit http://www.hlxcommunity.com
use POSIX;
use IO::Socket;
use Socket;
use Encode;
do "$::opt_libdir/HLstats_GameConstants.plib";
sub new
{
my ($class_name, $serverId, $address, $port, $server_name, $rcon_pass, $game, $publicaddress, $gameengine, $realgame, $maxplayers) = @_;
my ($self) = {};
bless($self, $class_name);
$self->{id} = $serverId;
$self->{address} = $address;
$self->{port} = $port;
$self->{game} = $game;
$self->{rcon} = $rcon_pass;
$self->{srv_players} = ();
# Game Engine
# HL1 - 1
# HL2 (original) - 2
# HL2ep2 ("OrangeBox") - 3
$self->{game_engine} = $gameengine;
$self->{rcon_obj} = undef;
$self->{name} = $server_name;
$self->{auto_ban} = 0;
$self->{contact} = "";
$self->{hlstats_url} = "";
$self->{publicaddress} = $publicaddress;
$self->{play_game} = -1;
$self->{last_event} = 0;
$self->{last_check} = 0;
$self->{lines} = 0;
$self->{map} = "";
$self->{numplayers} = 0;
$self->{num_trackable_players} = 0;
$self->{minplayers} = 6;
$self->{maxplayers} = $maxplayers;
$self->{difficulty} = 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
# Messaging commands
$self->{show_stats} = 0;
$self->{broadcasting_events} = 0;
$self->{broadcasting_player_actions} = 0;
$self->{broadcasting_command} = "";
$self->{broadcasting_command_announce} = "say";
$self->{player_events} = 1;
$self->{player_command} = "say";
$self->{player_command_osd} = "";
$self->{player_command_hint} = "";
$self->{player_admin_command} = 0;
$self->{default_display_events} = 1;
$self->{browse_command} = "";
$self->{swap_command} = "";
$self->{exec_command} = "";
$self->{global_chat_command} = "say";
# Message format operators
$self->{format_color} = "";
$self->{format_action} = "";
$self->{format_actionend} = "";
$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} = 1;
$self->{tk_penalty} = 0;
$self->{suicide_penalty} = 0;
$self->{skill_mode} = 0;
$self->{game_type} = 0;
$self->{bonusroundignore} = 0;
$self->{bonusroundtime} = 0;
$self->{bonusroundtime_ts} = 0;
$self->{bonusroundtime_state} = 0;
$self->{lastdisabledbonus} = $::ev_unixtime;
$self->{mod} = "";
$self->{switch_admins} = 0;
$self->{public_commands} = 1;
$self->{connect_announce} = 1;
$self->{update_hostname} = 0;
$self->{lastblueflagdefend} = 0;
$self->{lastredflagdefend} = 0;
# location hax
$self->{nextkillx} = "";
$self->{nextkilly} = "";
$self->{nextkillz} = "";
$self->{nextkillvicx} = "";
$self->{nextkillvicy} = "";
$self->{nextkillvicz} = "";
$self->{nextkillheadshot} = 0;
$self->{next_timeout} = 0;
$self->{next_flush} = 0;
$self->{next_plyr_flush} = 0;
$self->{needsupdate} = 0;
$self->set_play_game($realgame);
if ($self->{rcon})
{
$self->init_rcon();
}
$self->updateDB();
$self->update_server_loc();
return $self;
}
sub set_play_game
{
my ($self, $realgame) = @_;
if (exists($gamecode_to_game{$realgame}))
{
$self->{play_game} = $gamecode_to_game{$realgame};
}
}
sub is_admin
{
my($self, $steam_id) = @_;
for (@{$self->{admins}}) {
if ($_ eq $steam_id) {
return 1;
}
}
return 0;
}
sub get_game_mod_opts
{
# Runs immediately after server object is created and options are loaded.
my($self) = @_;
if ($self->{mod} ne "") {
my $mod = $self->{mod};
if ($mod eq "SOURCEMOD") {
$self->{browse_command} = "hlx_sm_browse";
$self->{swap_command} = "hlx_sm_swap";
$self->{global_chat_command} = "hlx_sm_psay";
$self->setHlxCvars();
} elsif ($mod eq "MANI") {
$self->{browse_command} = "ma_hlx_browse";
$self->{swap_command} = "ma_swapteam";
$self->{exec_command} = "ma_cexec";
$self->{global_chat_command} = "ma_psay";
} elsif ($mod eq "AMXX") {
$self->{browse_command} = "hlx_amx_browse";
$self->{swap_command} = "hlx_amx_swap";
$self->{global_chat_command} = "hlx_amx_psay";
$self->setHlxCvars();
} elsif ($mod eq "BEETLE") {
$self->{browse_command} = "hlx_browse";
$self->{swap_command} = "hlx_swap";
$self->{exec_command} = "hlx_exec";
$self->{global_chat_command} = "admin_psay";
} elsif ($mod eq "MINISTATS") {
$self->{browse_command} = "ms_browse";
$self->{swap_command} = "ms_swap";
$self->{global_chat_command} = "ms_psay";
}
# Turn on color and add game-specific color modifiers for when using hlx:ce sourcemod plugin
if (($self->{mod} eq "SOURCEMOD" &&
(
$self->{play_game} == CSS()
|| $self->{play_game} == TF()
|| $self->{play_game} == L4D()
|| $self->{play_game} == DODS()
|| $self->{play_game} == HL2MP()
|| $self->{play_game} == AOC()
|| $self->{play_game} == ZPS()
|| $self->{play_game} == FF()
|| $self->{play_game} == GES()
|| $self->{play_game} == FOF()
|| $self->{play_game} == PVKII()
|| $self->{play_game} == CSP()
|| $self->{play_game} == NUCLEARDAWN()
|| $self->{play_game} == DDD()
)
)
|| ($self->{mod} eq "AMXX"
&& $self->{play_game} == CSTRIKE())
) {
$self->{format_color} = " 1";
if ($self->{play_game} == ZPS() || $self->{play_game} == GES()) {
$self->{format_action} = "\x05";
} elsif ($self->{play_game} == FF()) {
$self->{format_action} = "^4";
} else {
$self->{format_action} = "\x04";
}
if ($self->{play_game} == FF()) {
$self->{format_actionend} = "^0";
} else {
$self->{format_actionend} = "\x01";
}
}
# Insurgency can only do one solid color afaik. The rest is handled in the plugin
if ($self->{mod} eq "SOURCEMOD" && $self->{play_game} == INSMOD()) {
$self->{format_color} = " 1";
}
}
}
sub format_userid {
my($self, $userid) = @_;
if ($self->{mod} eq "AMXX") {
return "#".$userid;
}
return "\"".$userid."\"";
}
sub quoteparam {
my($self, $message) = @_;
$message =~ s/'/ ' /g;
$message =~ s/"/ '' /g;
if (($self->{game_engine} != 2 || $self->{mod} eq "SOURCEMOD") && $self->{mod} ne "MANI") {
return "\"".$message."\"";
}
return $message;
}
#
# Set property 'key' to 'value'
#
sub set
{
my ($self, $key, $value) = @_;
if (defined($self->{$key}))
{
if ($self->{$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//i;
$self->{ingame_url} =~ s/\/$//;
&::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) = @_;
if ($amount) {
$amount = int($amount);
} else {
$amount = 1
}
my $value = $self->{$key};
$self->set($key, $value + $amount);
}
sub init_rcon
{
my ($self) = @_;
my $server_ip = $self->{address};
my $server_port = $self->{port};
my $rcon_pass = $self->{rcon};
my $game = $self->{game};
if ($::g_rcon && $rcon_pass) {
if ($self->{game_engine} == 1) {
$self->{rcon_obj} = new BASTARDrcon($self);
} else {
$self->{rcon_obj} = new TRcon($self);
}
}
if ($self->{rcon_obj}) {
&::printEvent ("SERVER", "Connecting to rcon on $server_ip:$server_port ... ok");
#&::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->{rcon_obj};
if (($rcon_obj) && ($::g_rcon == 1) && ($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 dorcon_multi
{
my ($self, @commands) = @_;
my $result;
my $rcon_obj = $self->{rcon_obj};
if (($rcon_obj) && ($::g_rcon == 1) && ($self->{rcon} ne "")) {
if ($self->{game_engine} > 1)
{
my $fullcmd = "";
foreach (@commands)
{
# replace ; to avoid executing multiple rcon commands.
my $cmd = $_;
$cmd =~ s/;//g;
$fullcmd .="$cmd;";
}
&::printNotice("RCON", $fullcmd, 1);
$result = $rcon_obj->execute($fullcmd);
}
else
{
foreach (@commands)
{
&::printNotice("RCON", $_, 1);
$result = $rcon_obj->execute($_);
}
}
} else {
&::printNotice("Rcon error: No Object available");
}
return $result;
}
sub rcon_getaddress
{
my ($self, $uniqueid) = @_;
my $result;
my $rcon_obj = $self->{rcon_obj};
if (($rcon_obj) && ($::g_rcon == 1) && ($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->{rcon_obj};
my $map_result = "";
my $max_player_result = -1;
my $servhostname = "";
my $difficulty = 0;
if (($rcon_obj) && ($::g_rcon == 1) && ($self->{rcon} ne "")) {
($servhostname, $map_result, $max_player_result, $difficulty) = $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, $servhostname, $difficulty);
}
sub rcon_getplayers
{
my ($self) = @_;
my %result;
my $rcon_obj = $self->{rcon_obj};
if (($rcon_obj) && ($::g_rcon == 1) && ($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->{track_server_load} > 0))
{
my $last_timestamp = $self->{track_server_timestamp};
my $new_timestamp = time();
if ($last_timestamp > 0)
{
if ($last_timestamp+299 < $new_timestamp)
{
# fetch fps and uptime via rcon
# Old style stats output:
#$string = " 0.00 0.00 0.00 54 1 249.81 0 dhjdsk";
# New style stats output:
#CPU In (KB/s) Out (KB/s) Uptime Map changes FPS Players Connects
#0.00 0.00 0.00 0 0 00.00 0 0
$string = $self->dorcon("stats");
# Remove first line of output
$string =~ /CPU.*\n(.*)\n*L{0,1}.*\Z/;
$string = $1;
# Grab FPS and Uptime from the output
$string =~ /([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s*([^ ]*)/;
$uptime = $4;
$fps = $6;
my $act_players = $self->{numplayers};
my $max_players = $self->{maxplayers};
if ($max_players > 0) {
if ($act_players > $max_players) {
$act_players = $max_players;
}
}
&::execCached("flush_server_load",
"INSERT IGNORE INTO hlstats_server_load
SET
server_id=?,
timestamp=?,
act_players=?,
min_players=?,
max_players=?,
map=?,
uptime=?,
fps=?",
$self->{id},
$new_timestamp,
$act_players,
$self->{minplayers},
$max_players,
$self->{map},
(($uptime)?$uptime:0),
(($fps)?$fps:0)
);
$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->{rcon_obj};
$rcmd = $self->{broadcasting_command_announce};
if (($::g_stdin == 0) && ($rcon_obj) && ($self->{rcon} ne ""))
{
if ($self->{broadcasting_events} == 1)
{
my $hpk = sprintf("%.0f", 0);
if ($self->{total_kills} > 0) {
$hpk = sprintf("%.2f", (100/$self->{total_kills})*$self->{total_headshots});
}
if ($rcmd ne "") {
$self->dorcon("$rcmd ".$self->quoteparam("HLstatsX:CE - Tracking ".&::number_format($self->{players})." players with ".&::number_format($self->{total_kills})." kills and ".&::number_format($self->{total_headshots})." headshots ($hpk%)"));
} else {
$self->messageAll("HLstatsX:CE - 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, $fromupdate) = @_;
if ($::g_stdin == 0) {
if ((time() - $self->{last_check})>120) {
$self->{last_check} = time();
&::printNotice("get_rcon_status");
my $temp_map = "";
my $temp_maxplayers = -1;
my $servhostname = "";
my $difficulty = 0;
my $update = 0;
if ($self->{rcon_obj})
{
($temp_map, $temp_maxplayers, $servhostname, $difficulty) = $self->rcon_getStatus();
if ($temp_map eq "") {
goto STATUSFAIL;
}
if ($self->{map} ne $temp_map) {
$self->{map} = $temp_map;
$update++;
}
if (($temp_maxplayers != -1) && ($temp_maxplayers > 0) && ($temp_maxplayers ne "")) {
if ($self->{maxplayers} != $temp_maxplayers) {
$self->{maxplayers} = $temp_maxplayers;
$update++;
}
}
if (($difficulty > 0) && ($self->{play_game} == L4D())) {
$self->{difficulty} = $difficulty;
}
if (($self->{update_hostname} > 0) && ($self->{name} ne $servhostname) && ($servhostname ne "")) {
$self->{name} = $servhostname;
$update++;
}
}
else
{ # no rcon or status command failed
STATUSFAIL:
my ($querymap, $queryhost, $querymax) = &::queryServer($self->{address}, $self->{port}, 'mapname', 'hostname', 'maxplayers');
if ($querymap ne "") {
$self->{map} = $querymap;
$update++;
#if map is blank, query likely failed as a whole
if (($querymax != -1) && ($querymax > 0)) {
if ($self->{maxplayers} != $querymax) {
$self->{maxplayers} = $querymax;
$update++;
}
}
if ($self->{update_hostname} > 0 && $queryhost ne "" && $self->{name} ne $queryhost) {
$self->{name} = $queryhost;
$update++;
}
}
}
if ($update > 0 && $fromupdate == 0) {
$self->updateDB();
}
&::printNotice("get_rcon_status successfully");
}
}
return $self->{map};
}
sub update_players_pings
{
my ($self) = @_;
if ($self->{num_trackable_players} < $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(%{$self->{srv_players}}) )
{
my $uniqueid = $player->{uniqueid};
if (defined($players{$uniqueid}))
{
if ($player->{is_bot} == 0 && $player->{userid} > 0) {
my $ping = $players{$uniqueid}->{"Ping"};
$player->set("ping", $ping);
if ($ping > 0) {
&::recordEvent(
"Latency", 0,
$player->{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->{address}.":".$self->{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 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, $name) = @_;
my $rcmd = $self->{player_command_hint};
$self->dorcon($self->{swap_command}." ".$self->format_userid($playerid));
if ($self->{player_command_hint} eq "") {
$rcmd = $self->{player_command};
}
$self->dorcon(sprintf("%s %s %s", $rcmd, $self->format_userid($playerid), $self->quoteparam("HLstatsX:CE - You were switched to balance teams")));
if ($self->{player_admin_command} ne "") {
$self->dorcon(sprintf("%s %s",$self->{player_admin_command}, $self->quoteparam("HLstatsX:CE - $name was switched to balance teams")));
}
}
sub analyze_teams
{
my ($self) = @_;
if (($::g_stdin == 0) && ($self->{num_trackable_players} < $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(%{$self->{srv_players}}) )
{
my @Player = ( $player->{name}, #0
$player->{uniqueid}, #1
$player->{skill}, #2
$player->{team}, #3
$player->{map_kills}, #4
$player->{map_deaths}, #5
($player->{map_kills}-$player->{map_deaths}), #6
$player->{is_dead}, #7
$player->{userid}, #8
);
if ($Player[3] eq "TERRORIST")
{
push(@{$ts_players[$ts_count]}, @Player);
$ts_skill += $Player[2];
$ts_count += 1;
$ts_kills += $Player[4];
$ts_deaths += $Player[5];
}
elsif ($Player[3] eq "CT")
{
push(@{$ct_players[$ct_count]}, @Player);
$ct_skill += $Player[2];
$ct_count += 1;
$ct_kills += $Player[4];
$ct_deaths += $Player[5];
}
}
@ct_players = sort { $b->[6] <=> $a->[6]} @ct_players;
@ts_players = sort { $b->[6] <=> $a->[6]} @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->{player_events} == 1)
{
if ($self->{player_admin_command} ne "") {
$cmd_str = $self->{player_admin_command}." $admin_msg";
$self->dorcon($cmd_str);
}
}
$self->messageAll("HLstatsX:CE - ATB - Checking Teams", 0, 1);
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}[7] == 1) # only dead players!!
{
if (($self->{switch_admins} == 1) || (($self->{switch_admins} == 0) && ($self->is_admin(@{$entry}[1]) == 0))) {
$self->switch_player(@{$entry}[8], @{$entry}[0]);
$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->[6] <=> $a->[6]} @ct_players; # best player
}
else
{
@ct_players = sort { $a->[6] <=> $b->[6]} @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}[7] == 1) # only dead players!!
{
if (($self->{switch_admins} == 1) || (($self->{switch_admins} == 0) && ($self->is_admin(@{$entry}[1]) == 0))) {
$self->switch_player(@{$entry}[8], @{$entry}[0]);
$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->[6] <=> $a->[6]} @ts_players; # best player
foreach my $entry (@ts_players)
{
if ($ts_found == 0)
{
if (@{$entry}[7] == 1) # only dead players!!
{
if (($self->{switch_admins} == 1) || (($self->{switch_admins} == 0) && ($self->is_admin(@{$entry}[1]) == 0))) {
$self->{ba_last_swap} = 3;
$self->switch_player(@{$entry}[9], @{$entry}[0]);
$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->[6] <=> $a->[6]} @ts_players; # best player
foreach my $entry (@ts_players)
{
if ($ts_found == 0)
{
if (@{$entry}[7] == 1) # only dead players!!
{
if (($self->{switch_admins} == 1) || (($self->{switch_admins} == 0) && ($self->is_admin(@{$entry}[1]) == 0))) {
$ts_playerid = @{$entry}[8];
$ts_name = @{$entry}[0];
$ts_found++;
}
}
}
}
my $ct_found = 0;
@ct_players = sort { $a->[6] <=> $b->[6]} @ct_players; # worst player
foreach my $entry (@ct_players)
{
if ($ct_found == 0)
{
if (@{$entry}[7] == 1) # only dead players!!
{
if (($self->{switch_admins} == 1) || (($self->{switch_admins} == 0) && ($self->is_admin(@{$entry}[1]) == 0))) {
$ct_playerid = @{$entry}[8];
$ct_name = @{$entry}[0];
$ct_found++;
}
}
}
}
if (($ts_found>0) && ($ct_found>0))
{
$self->{ba_last_swap} = 3;
$self->switch_player($ts_playerid, $ts_name);
$self->switch_player($ct_playerid, $ct_name);
}
}
}
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->[6] <=> $a->[6]} @ct_players; # best player
foreach my $entry (@ct_players)
{
if ($ct_found == 0)
{
if (@{$entry}[7] == 1) # only dead players!!
{
if (($self->{switch_admins} == 1) || (($self->{switch_admins} == 0) && ($self->is_admin(@{$entry}[1]) == 0))) {
$self->{ba_last_swap} = 3;
$self->switch_player(@{$entry}[8], @{$entry}[0]);
$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->[6] <=> $a->[6]} @ct_players; # best player
foreach my $entry (@ct_players)
{
if ($ct_found == 0)
{
if (@{$entry}[7] == 1) # only dead players!!
{
if (($self->{switch_admins} == 1) || (($self->{switch_admins} == 0) && ($self->is_admin(@{$entry}[1]) == 0))) {
$ct_playerid = @{$entry}[8];
$ct_name = @{$entry}[0];
$ct_found++;
}
}
}
}
my $ts_found = 0;
@ts_players = sort { $a->[6] <=> $b->[6]} @ts_players; # worst player
foreach my $entry (@ts_players)
{
if ($ts_found == 0)
{
if (@{$entry}[7] == 1) # only dead players!!
{
if (($self->{switch_admins} == 1) || (($self->{switch_admins} == 0) && ($self->is_admin(@{$entry}[1]) == 0))) {
$ts_playerid = @{$entry}[8];
$ts_name = @{$entry}[0];
$ts_found++;
}
}
}
}
if (($ts_found > 0) && ($ct_found > 0))
{
$self->{ba_last_swap} = 3;
$self->switch_player($ts_playerid, $ts_name);
$self->switch_player($ct_playerid, $ct_name);
}
}
}
}
} # end if rounds > 1
}
}
#
# Marks server as needing flush
#
sub updateDB
{
my ($self) = @_;
$self->{needsupdate} = 1;
}
#
# Flushes server information in database
#
sub flushDB
{
my ($self) = @_;
$self->get_map(1);
my $serverid = $self->{id};
if ($self->{total_kills} == 0)
{
my $result = &::execCached(
"get_server_player_info",
"SELECT
kills,
headshots,
suicides,
rounds,
ct_shots+ts_shots as shots,
ct_hits+ts_hits as hits
FROM
hlstats_Servers
WHERE
serverId=?",
$self->{id}
);
($self->{total_kills}, $self->{total_headshots}, $self->{total_suicides},$self->{total_rounds},$self->{total_shots},$self->{total_hits}) = $result->fetchrow_array();
$result->finish;
}
my $result = &::execCached(
"get_player_count",
"SELECT count(*) as players FROM hlstats_Players WHERE game=? and hideranking<>2",
&::quoteSQL($self->{game}));
$self->{players} = $result->fetchrow_array();
$result->finish;
# Update player details
my $query = "
UPDATE
hlstats_Servers
SET
name=?,
rounds=rounds + ?,
kills=kills + ?,
suicides=suicides + ?,
headshots=headshots + ?,
bombs_planted=bombs_planted + ?,
bombs_defused=bombs_defused + ?,
players=?,
ct_wins=ct_wins + ?,
ts_wins=ts_wins + ?,
act_players=?,
max_players=?,
act_map=?,
map_rounds=?,
map_ct_wins=?,
map_ts_wins=?,
map_started=?,
map_changes=map_changes + ?,
ct_shots=ct_shots + ?,
ct_hits=ct_hits + ?,
ts_shots=ts_shots + ?,
ts_hits=ts_hits + ?,
map_ct_shots=?,
map_ct_hits=?,
map_ts_shots=?,
map_ts_hits=?,
last_event=?
WHERE
serverId=?
";
my @vals = (
&::quoteSQL($self->{name}),
$self->{rounds},
$self->{kills},
$self->{suicides},
$self->{headshots},
$self->{bombs_planted},
$self->{bombs_defused},
$self->{players},
$self->{ct_wins},
$self->{ts_wins},
$self->{numplayers},
$self->{maxplayers},
&::quoteSQL($self->{map}),
$self->{map_rounds},
$self->{map_ct_wins},
$self->{map_ts_wins},
$self->{map_started},
$self->{map_changes},
$self->{ct_shots},
$self->{ct_hits},
$self->{ts_shots},
$self->{ts_hits},
$self->{map_ct_shots},
$self->{map_ct_hits},
$self->{map_ts_shots},
$self->{map_ts_hits},
$::ev_unixtime,
$serverid
);
&::execCached("update_server_stats", $query, @vals);
$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);
$self->{needsupdate} = 0;
}
sub flush_player_count
{
my ($self) = @_;
&::execCached("flush_plyr_cnt",
"UPDATE hlstats_Servers SET act_players=? WHERE serverId=?",
$self->{numplayers},
$self->{id}
);
}
sub update_server_loc
{
my ($self) = @_;
my $serverid = $self->{id};
my $server_ip = $self->{address};
my $publicaddress = $self->{publicaddress};
if ($publicaddress =~ /^(\d+\.\d+\.\d+\.\d+)/) {
$server_ip = $publicaddress;
} elsif ($publicaddress =~ /^([0-9a-zA-Z\-\.]+)\:*.*/) {
my $hostip = inet_aton($1);
if ($hostip) {
$server_ip = inet_ntoa($hostip);
}
}
my $found = 0;
my $servcity = "";
my $servcountry = "";
my $servlat=undef;
my $servlng=undef;
if ($::g_geoip_binary > 0) {
if(!defined($::g_gi)) {
return;
}
my ($country_code, $country_code3, $country_name, $region, $city, $postal_code, $latitude, $longitude,
$metro_code, $area_code) = $::g_gi->get_city_record($server_ip);
if ($longitude) {
$found++;
$servcity = ((defined($city))?encode("utf8",$city):"");
$servcountry = ((defined($country_name))?encode("utf8",$country_name):"");
$servlat = $latitude;
$servlng = $longitude;
}
} else {
my @ipp = split (/\./,$server_ip);
my $ip_number = $ipp[0]*16777216+$ipp[1]*65536+$ipp[2]*256+$ipp[3];
my $query = "
SELECT locId FROM geoLiteCity_Blocks WHERE startIpNum<= $ip_number AND endIpNum>= $ip_number LIMIT 1";
my $result = &::doQuery($query);
if ($result->rows > 0) {
my $locid = $result->fetchrow_array;
$result->finish;
my $query = "
SELECT
city,
name AS country,
latitude AS lat,
longitude AS lng
FROM
geoLiteCity_Location a
INNER JOIN
hlstats_Countries b ON a.country=b.flag
WHERE
locId= $locid
LIMIT 1";
my $result = &::doQuery($query);
if ($result->rows > 0) {
$found++;
($servcity,$servcountry,$servlat,$servlng) = $result->fetchrow_array;
$result->finish;
}
}
}
if ($found > 0) {
my $query = "
UPDATE
`hlstats_Servers`
SET
city = '".(defined($servcity)?&::quoteSQL($servcity):"")."',
country='".(defined($servcountry)?&::quoteSQL($servcountry):"")."',
lat=".((defined($servlat))?$servlat:"NULL").",
lng=".((defined($servlng))?$servlng:"NULL")."
WHERE
serverId =$serverid
";
&::execNonQuery($query);
}
}
sub messageAll
{
my($self, $msg, $noshow, $force) = @_;
if ($self->{broadcasting_events} == 1 || $force == 1)
{
if ($self->{mod} eq "SOURCEMOD" || $self->{mod} eq "AMXX")
{
my @userlist;
foreach $player (values(%{$self->{srv_players}}))
{
if (($player->{is_bot} == 0) && ($player->{userid} > 0) && ($player->{playerid} != $noshow) && ($player->{display_events} == 1 || $force == 1))
{
push(@userlist, $player->{userid});
}
}
if ($self->{play_game} != FF())
{
$msg = $self->{format_action}.$msg;
}
$self->messageMany($msg, 1, @userlist);
}
else
{
$self->dorcon("say ".$msg);
}
}
}
sub messageMany
{
my($self, $msg, $toall, @userlist) = @_;
if (scalar(@userlist) > 0)
{
if ($self->{mod} eq "SOURCEMOD")
{
my $usersendlist = "";
foreach (@userlist)
{
$usersendlist .= $_.",";
}
$usersendlist =~ s/,$//;
my $color = $self->{format_color};
if ($toall > 0 && $color eq " 1")
{
$color = " 2";
}
$self->dorcon($self->{player_command}." \"$usersendlist\"$color ".$self->quoteparam($msg));
}
elsif ($self->{mod} eq "AMXX")
{
while (@userlist)
{
my $usersendlist = "";
for ($i = 0; $i < 8; $i++)
{
$usersendlist .= shift(@userlist);
if ($i < 7)
{
$usersendlist .= ",";
}
}
$self->dorcon("hlx_amx_bulkpsay \"$usersendlist\"".$self->{format_color}." ".$self->quoteparam($msg));
}
}
else
{
$rcmd = $self->{broadcasting_command};
foreach (@userlist)
{
$self->dorcon(sprintf("%s %s%s %s",$rcmd, $self->format_userid($_), $self->{format_color}, $self->quoteparam($msg)));
}
}
}
}
sub setHlxCvars
{
my ($self) = @_;
if ($self->{hlstats_url} ne "")
{
$self->dorcon("hlxce_webpage \"".$self->{hlstats_url}."\"");
}
$self->dorcon("hlxce_version \"".$::g_version."\"");
if ($self->{play_game} eq "MANI" && $self->dorcon("mani_hlx_prefix" =~ /gameme/i))
{
$self->dorcon("mani_hlx_prefix \"HLstatsX\"");
}
}
sub updatePlayerCount
{
my ($self) = @_;
if ($::g_debug > 1) {
&::printEvent("SERVER","Updating Player Count");
}
my $trackable = 0;
if ($self->{play_game} == L4D()) {
my $num = 0;
while (my($pl, $player) = each(%{$self->{srv_players}})) {
if ($player->{trackable} == 1) {
$trackable++;
}
if ($player->{userid} > 0) {
$num++;
}
}
$self->{numplayers} = $num;
$self->{num_trackable_players} = $trackable;
} else {
$self->{numplayers} = scalar keys %{$self->{srv_players}};
while (my($pl, $player) = each(%{$self->{srv_players}})) {
if ($player->{trackable} == 1) {
$trackable++;
}
}
$self->{num_trackable_players} = $trackable;
}
$self->flush_player_count();
}
1;