mirror of
https://gitlab.com/xonotic/xonotic
synced 2024-12-15 11:24:56 +00:00
181 lines
4.3 KiB
Perl
Executable File
181 lines
4.3 KiB
Perl
Executable File
#!/usr/bin/perl
|
|
|
|
# no warranty for this script
|
|
# and no documentation
|
|
# take it or leave it
|
|
|
|
use strict;
|
|
use warnings;
|
|
use FindBin; use lib $FindBin::Bin;
|
|
use IO::Socket;
|
|
use Socket;
|
|
use sigtrap qw(die normal-signals);
|
|
use WeaponEncounterProfile;
|
|
|
|
my ($statsfile) = @ARGV;
|
|
my $password = $ENV{rcon_password};
|
|
my $server = $ENV{rcon_address};
|
|
my $bind = $ENV{rcon_bindaddress};
|
|
|
|
my $stats;
|
|
|
|
sub AddKill($$$$$)
|
|
{
|
|
my ($addr, $map, $attackerweapon, $targweapon, $type) = @_;
|
|
$stats->event($addr, $map, $attackerweapon, $targweapon, $type);
|
|
}
|
|
|
|
sub StoreData()
|
|
{
|
|
$stats->save();
|
|
}
|
|
|
|
sub LoadData()
|
|
{
|
|
$stats = WeaponEncounterProfile->new($statsfile);
|
|
}
|
|
|
|
$SIG{ALRM} = sub
|
|
{
|
|
print STDERR "Operation timed out.\n";
|
|
exit 1;
|
|
};
|
|
|
|
our @discosockets = ();
|
|
sub LogDestUDP($)
|
|
{
|
|
# connects to a DP server using rcon with log_dest_udp
|
|
my ($sock) = @_;
|
|
my $value = sprintf "%s:%d", $sock->sockhost(), $sock->sockport();
|
|
$sock->send("\377\377\377\377rcon $password log_dest_udp", 0)
|
|
or die "send rcon: $!";
|
|
alarm 15;
|
|
for(;;)
|
|
{
|
|
$sock->recv(my $response, 2048, 0)
|
|
or die "recv: $!";
|
|
if($response =~ /^\377\377\377\377n"log_dest_udp" is "(.*)" \[".*"\]\n$/s)
|
|
{
|
|
alarm 0;
|
|
my @dests = split /\s+/, $1;
|
|
return
|
|
if grep { $_ eq $value } @dests;
|
|
push @dests, $value;
|
|
$sock->send("\377\377\377\377rcon $password log_dest_udp \"@dests\"");
|
|
last;
|
|
}
|
|
}
|
|
alarm 0;
|
|
push @discosockets, [$sock, $value];
|
|
|
|
END
|
|
{
|
|
for(@discosockets)
|
|
{
|
|
my ($s, $v) = @$_;
|
|
# disconnects (makes the server stop send the data to us)
|
|
$s->send("\377\377\377\377rcon $password log_dest_udp", 0)
|
|
or die "send rcon: $!";
|
|
alarm 15;
|
|
for(;;)
|
|
{
|
|
$s->recv(my $response, 2048, 0)
|
|
or die "recv: $!";
|
|
if($response =~ /^\377\377\377\377n"log_dest_udp" is "(.*)" \[".*"\]\n$/s)
|
|
{
|
|
alarm 0;
|
|
my @dests = split /\s+/, $1;
|
|
return
|
|
if not grep { $_ eq $v } @dests;
|
|
@dests = grep { $_ ne $v } @dests;
|
|
$s->send("\377\377\377\377rcon $password log_dest_udp \"@dests\"");
|
|
last;
|
|
}
|
|
}
|
|
alarm 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
sub sockaddr_readable($)
|
|
{
|
|
my ($binary) = @_;
|
|
my ($port, $addr) = sockaddr_in $binary;
|
|
return sprintf "%s:%d", inet_ntoa($addr), $port;
|
|
}
|
|
|
|
my $sock;
|
|
if(defined $bind)
|
|
{
|
|
# bind to a port and wait for any packets
|
|
$sock = IO::Socket::INET->new(Proto => 'udp', LocalAddr => $bind, LocalPort => 26000)
|
|
or die "socket: $!";
|
|
}
|
|
else
|
|
{
|
|
# connect to a DP server
|
|
$sock = IO::Socket::INET->new(Proto => 'udp', PeerAddr => $server, PeerPort => 26000)
|
|
or die "socket: $!";
|
|
LogDestUDP $sock;
|
|
}
|
|
my %currentmap = ();
|
|
|
|
my %bots = ();
|
|
|
|
LoadData();
|
|
while(my $addr = sockaddr_readable $sock->recv($_, 2048, 0))
|
|
{
|
|
$addr = $server
|
|
if not defined $bind;
|
|
s/^\377\377\377\377n//
|
|
or next;
|
|
for(split /\r?\n/, $_)
|
|
{
|
|
if(/^:gamestart:([^:]+):/)
|
|
{
|
|
StoreData();
|
|
$currentmap{$addr} = $1;
|
|
$bots{$addr} = {};
|
|
print "($addr) switching to $1\n";
|
|
next;
|
|
}
|
|
|
|
next
|
|
unless defined $currentmap{$addr};
|
|
if(/^:join:(\d+):bot:/)
|
|
{
|
|
$bots{$addr}{$1} = 1;
|
|
}
|
|
elsif(/^:kill:frag:(\d+):(\d+):type=(\d+):items=(\d+)([A-Z]*)(?:|(\d+)):victimitems=(\d+)([A-Z]*)(?:|(\d+))$/)
|
|
{
|
|
my ($a, $b, $type, $killweapon, $killflags, $killrunes, $victimweapon, $victimflags, $victimrules) = ($1, $2, $3, $4, $5, $6, $7, $8, $9);
|
|
next
|
|
if exists $bots{$addr}{$a} or exists $bots{$addr}{$b}; # only count REAL kills
|
|
$type &= 0xFF
|
|
if $type < 10000;
|
|
$killweapon = $type
|
|
if $stats->weaponid_valid($type); # if $type is not a weapon deathtype, count the weapon of the killer
|
|
$killweapon = 0
|
|
if not $stats->weaponid_valid($killweapon); # invalid weapon? that's 0 then
|
|
$victimweapon = 0
|
|
if not $stats->weaponid_valid($victimweapon); # dito
|
|
next
|
|
if $killflags =~ /S|I/ or $victimflags =~ /T/; # no strength, shield or typekills (these skew the statistics)
|
|
AddKill($addr, $currentmap{$addr}, $killweapon, $victimweapon, +1);
|
|
}
|
|
elsif(/^:kill:suicide:\d+:\d+:type=(\d+):items=(\d+)([A-Z]*)(?:|(\d+))$/)
|
|
{
|
|
my ($type, $killweapon, $killflags, $killrunes) = ($1, $2, $3, $4, $5, $6, $7);
|
|
$type &= 0xFF
|
|
if $type < 10000;
|
|
$killweapon = $type
|
|
if $stats->weaponid_valid($type);
|
|
$killweapon = 0
|
|
if not $stats->weaponid_valid($killweapon);
|
|
next
|
|
if $killflags =~ /S/; # no strength suicides (happen too easily accidentally)
|
|
AddKill($addr, $currentmap{$addr}, $killweapon, $killweapon, +1);
|
|
}
|
|
}
|
|
}
|