File Coverage

blib/lib/Net/Halo/Status.pm
Criterion Covered Total %
statement 62 156 39.7
branch 14 48 29.1
condition 2 50 4.0
subroutine 10 16 62.5
pod 0 9 0.0
total 88 279 31.5


line stmt bran cond sub pod time code
1             package Net::Halo::Status;
2              
3 1     1   40079 use strict;
  1         3  
  1         43  
4 1     1   6 use warnings;
  1         2  
  1         34  
5 1     1   5 use Carp qw(croak);
  1         8  
  1         226  
6 1     1   1138 use IO::Socket::INET;
  1         45680  
  1         8  
7 1     1   2000 use Data::Dumper;
  1         11939  
  1         78  
8 1     1   1048 use Encode;
  1         13216  
  1         2536  
9              
10             require Exporter;
11              
12             our @ISA = qw(Exporter);
13             our @EXPORT = qw();
14             our %EXPORT_TAGS = (all => []);
15             our @EXPORT_OK = (@{$EXPORT_TAGS{all}});
16              
17             our $VERSION = '0.02';
18              
19             our $player_flags = {
20             'NumberOfLives' => ['Infinite', 1, 3, 5],
21             'MaximumHealth' => ['50%', '100%', '150%', '200%', '300%', '400%'],
22             'Shields' => [1, 0],
23             'RespawnTime' => [0, 5, 10, 15],
24             'RespawnGrowth' => [0, 5, 10, 15],
25             'OddManOut' => [0, 1],
26             'InvisiblePlayers' => [0, 1],
27             'SuicidePenalty' => [0, 5, 10, 15],
28             'InfiniteGrenades' => [0, 1],
29             'WeaponSet' => [
30             'Normal', 'Pistols', 'Rifles', 'Plasma', 'Sniper', 'No Sniping',
31             'Rocket Launchers', 'Shotguns', 'Short Range', 'Human', 'Covenant',
32             'Classic', 'Heavy Weapons'
33             ],
34             'StartingEquipment' => ['Custom', 'Generic'],
35             'Indicator' => ['Motion Tracker', 'Nav Points', 'None'],
36             'OtherPlayersOnRadar' => ['No', 'All', undef, 'Friends'],
37             'FriendIndicators' => [0, 1],
38             'FriendlyFire' => ['Off', 'On', 'Shields Only', 'Explosives Only'],
39             'FriendlyFirePenalty' => [0, 5, 10, 15],
40             'AutoTeamBalance' => [0, 1],
41              
42             # Team Flags
43             'VehicleRespawn' => [0, 30, 60, 90, 120, 180, 300],
44             'RedVehicleSet' => [
45             'Default', undef, 'Warthogs', 'Ghosts', 'Scorpions', 'Rocket Warthogs',
46             'Banshees', 'Gun Turrets', 'Custom'
47             ],
48             'BlueVehicleSet' => [
49             'Default', undef, 'Warthogs', 'Ghosts', 'Scorpions', 'Rocket Warthogs',
50             'Banshees', 'Gun Turrets', 'Custom'
51             ],
52             };
53              
54             our $game_flags = {
55             'GameType' => [
56             'Capture the Flag', 'Slayer', 'Oddball', 'King of the Hill', 'Race'
57             ],
58             # CTF
59             'Assault' => [0, 1],
60             'FlagMustReset' => [0, 1],
61             'FlagAtHomeToScore' => [0, 1],
62             'SingleFlag' => [0, 60, 120, 180, 300, 600],
63             # Slayer
64             'DeathBonus' => [1, 0],
65             'KillPenalty' => [1, 0],
66             'KillInOrder' => [0, 1],
67             # Oddball
68             'RandomStart' => [0, 1],
69             'SpeedWithBall' => ['Slow', 'Normal', 'Fast'],
70             'TraitWithBall' => ['None', 'Invisible', 'Extra Damage', 'Damage Resistant'],
71             'TraitWithoutBall' => ['None', 'Invisible', 'Extra Damage', 'Damage Resistant'],
72             'BallType' => ['Normal', 'Reverse Tag', 'Juggernaut'],
73             'BallSpawnCount' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
74             # King of the Hill
75             'MovingHill' => [0, 1],
76             # Race
77             'RaceType' => ['Normal', 'Any Order', 'Rally'],
78             'TeamScoring' => ['Minimum', 'Maximum', 'Sum'],
79             };
80              
81             sub new {
82 1     1 0 12 my $class = shift;
83 1         4 my $self = bless {}, $class;
84              
85 1 50       5 croak "$class requires an even number of parameters" if @_ % 2;
86              
87 1         3 my %params = @_;
88              
89 1         3 my $timeout = delete $params{Timeout};
90 1 50 33     5 $timeout = 15 unless defined $timeout and $timeout >= 0;
91              
92 1         2 my $retry = delete $params{Retry};
93 1 50 33     5 $retry = 2 unless defined $retry and $retry >= 0;
94              
95 1         1 my $server = delete $params{Server};
96 1 50       4 $self->server($server) if defined $server;
97              
98 1         2 my $port = delete $params{Port};
99 1 50       6 $self->port(2302) unless defined $port; # Default to 2302.
100 1 50       3 $self->port($port) if defined $port;
101              
102 1 50       4 croak "$class doesn't know these parameters: ",
103             join(', ', sort(keys(%params))) if scalar(keys(%params));
104              
105 1         3 return $self;
106             }
107              
108             sub server ($) {
109 2     2 0 8 my $self = shift;
110 2 100       7 if (@_) { $self->{Server} = shift };
  1         3  
111 2         5 return $self->{Server};
112             }
113              
114             sub port ($) {
115 2     2 0 3 my $self = shift;
116 2 100       6 if (@_) { $self->{Port} = shift };
  1         9  
117 2         15 return $self->{Port};
118             }
119              
120              
121             sub query () {
122 1     1 0 6 my $self = shift;
123 1         3 my $data = ''; # Receiving data buffer.
124 1         46 my %data = ();
125              
126 1         2 eval {
127 1     0   33 local $SIG{ALRM} = sub { die "Timed Out\n" };
  0         0  
128 1         15 alarm 1;
129 1 50       4 my $sock = new IO::Socket::INET (
130             PeerAddr => $self->server(),
131             PeerPort => $self->port(),
132             Proto => 'udp',
133             Type => SOCK_DGRAM,
134             ReuseAddr => 1,
135             Blocking => 1,
136             ) or croak "IO::Socket::INET->new() failed to bind: $@\n";
137              
138 1         436 $sock->send("\xFE\xFD\x00\x33\x8f\x02\x00\xff\xff\xff");
139 1         138 $sock->recv($data, 16384);
140              
141 1         62 alarm 0;
142             };
143 1         5 alarm 0;
144              
145 1 50       4 if ($@) {
146 0         0 $data{ERROR} = $@;
147 0         0 return \%data;
148             }
149              
150 1 50       5 if ($data eq '') {
151 1         4 $data{ERROR} = 'DOWN';
152             } else {
153 0         0 $data =~ s/\x00+$//;
154 0         0 my ($rules, $players, $score) = ($data =~ /^.{5}(.+?)\x00{3}[\x00-\x10](.+)\x00{2}[\x02\x00](.+$)/);
155 0         0 my @parts = split(/\x00/, $data);
156 0         0 %{$data{'Rules'}} = split(/\x00/, $rules);
  0         0  
157 0         0 $data{'PlayerFlags'} = $self->decode_player_flags($data{'Rules'}{'player_flags'});
158 0         0 $data{'GameFlags'} = $self->decode_game_flags($data{'Rules'}{'game_flags'});
159 0         0 $data{'Players'} = $self->process_segment($players);
160 0         0 $data{'Score'} = $self->process_segment($score);
161             }
162              
163 1         1 foreach my $_Pflag (keys %{$data{PlayerFlags}->{Player}}) {
  1         6  
164 0         0 $data{PlayerFlags}->{Player}->{$_Pflag} = $self->halo_player_flag($_Pflag, $data{PlayerFlags}->{Player}->{$_Pflag});
165             }
166 1         3 foreach my $_Tflag (keys %{$data{PlayerFlags}->{Team}}) {
  1         4  
167 0         0 $data{PlayerFlags}->{Player}->{$_Tflag} = $self->halo_player_flag($_Tflag, $data{PlayerFlags}->{Team}->{$_Tflag});
168             }
169 1         2 foreach my $_Gflag (keys %{$data{GameFlags}}) {
  1         26  
170 0         0 $data{GameFlags}->{$_Gflag} = $self->halo_game_flag($_Gflag, $data{GameFlags}->{$_Gflag});
171             }
172              
173 1         4 return \%data;
174             }
175              
176             sub decode_player_flags {
177 0     0 0   my $self = shift;
178 0           my $str = shift;
179 0           my $flags = { };
180 0 0 0       return $flags if $str eq '' || $str !~ /^\d+\,\d+$/;
181              
182 0           my ($player, $vehicle) = split(/\,/, $str);
183              
184 0           $flags->{'Player'}->{'NumberOfLives'} = $player & 3;
185 0           $flags->{'Player'}->{'MaximumHealth'} = ($player >> 2) & 7;
186 0           $flags->{'Player'}->{'Shields'} = ($player >> 5) & 1;
187 0           $flags->{'Player'}->{'RespawnTime'} = ($player >> 6) & 3;
188 0           $flags->{'Player'}->{'RespawnGrowth'} = ($player >> 8) & 3;
189 0           $flags->{'Player'}->{'OddManOut'} = ($player >> 10) & 1;
190 0           $flags->{'Player'}->{'InvisiblePlayers'} = ($player >> 11) & 1;
191 0           $flags->{'Player'}->{'SuicidePenalty'} = ($player >> 12) & 3;
192 0           $flags->{'Player'}->{'InfiniteGrenades'} = ($player >> 14) & 1;
193 0           $flags->{'Player'}->{'WeaponSet'} = ($player >> 15) & 15;
194 0           $flags->{'Player'}->{'StartingEquipment'} = ($player >> 19) & 1;
195 0           $flags->{'Player'}->{'Indicator'} = ($player >> 20) & 3;
196 0           $flags->{'Player'}->{'OtherPlayersOnRadar'} = ($player >> 22) & 3;
197 0           $flags->{'Player'}->{'FriendIndicators'} = ($player >> 24) & 1;
198 0           $flags->{'Player'}->{'FriendlyFire'} = ($player >> 25) & 3;
199 0           $flags->{'Player'}->{'FriendlyFirePenalty'} = ($player >> 27) & 3;
200 0           $flags->{'Player'}->{'AutoTeamBalance'} = ($player >> 29) & 1;
201              
202 0           $flags->{'Team'}->{'VehicleRespawn'} = ($vehicle & 7);
203 0           $flags->{'Team'}->{'RedVehicleSet'} = ($vehicle >> 3) & 15;
204 0           $flags->{'Team'}->{'BlueVehicleSet'} = ($vehicle >> 7) & 15;
205              
206 0           return $flags;
207             }
208              
209             sub decode_game_flags {
210 0     0 0   my $self = shift;
211 0           my $str = shift;
212 0           my $flags = { };
213 0 0 0       return $flags if $str eq '' || $str !~ /^\d+$/;
214              
215 0           $flags->{'GameType'} = $str & 7;
216 0 0         if ($flags->{'GameType'} == 1) { # CTF
    0          
    0          
    0          
    0          
217 0   0       $flags->{'Assault'} = ($str >> 3) && 1;
218 0   0       $flags->{'FlagMustReset'} = ($str >> 5) && 1;
219 0   0       $flags->{'FlagAtHomeToScore'} = ($str >> 6) && 1;
220 0   0       $flags->{'SingleFlag'} = ($str >> 7) && 7;
221             } elsif ($flags->{'GameType'} == 2) { # Slayer
222 0   0       $flags->{'DeathBonus'} = ($str >> 3) && 1;
223 0   0       $flags->{'KillPenalty'} = ($str >> 5) && 1;
224 0   0       $flags->{'KillInOrder'} = ($str >> 6) && 1;
225             } elsif ($flags->{'GameType'} == 3) { # Oddball
226 0   0       $flags->{'RandomStart'} = ($str >> 3) && 1;
227 0   0       $flags->{'SpeedWithBall'} = ($str >> 5) && 3;
228 0   0       $flags->{'TraitWithBall'} = ($str >> 7) && 3;
229 0   0       $flags->{'TraitWithoutBall'} = ($str >> 9) && 3;
230 0   0       $flags->{'BallType'} = ($str >> 11) && 3;
231 0   0       $flags->{'BallSpawnCount'} = ($str >> 13) && 31;
232             } elsif ($flags->{'GameType'} == 4) { # Hill
233 0   0       $flags->{'MovingHill'} = ($str >> 3) && 1;
234             } elsif ($flags->{'GameType'} == 5) { # Race
235 0   0       $flags->{'RaceType'} = ($str >> 3) && 3;
236 0   0       $flags->{'TeamScoring'} = ($str >> 5) && 3;
237             }
238              
239 0           return $flags;
240             }
241              
242             sub halo_player_flag {
243 0     0 0   my $self = shift;
244 0           my $flag_name = shift;
245 0           my $flag_value = shift;
246              
247 0 0 0       if (defined($player_flags->{$flag_name}) &&
248             defined($player_flags->{$flag_name}->[$flag_value])) {
249 0           return $player_flags->{$flag_name}->[$flag_value];
250             } else {
251 0           return $flag_value;
252             }
253             }
254              
255             sub halo_game_flag {
256 0     0 0   my $self = shift;
257 0           my $flag_name = shift;
258 0           my $flag_value = shift;
259              
260 0 0 0       if(defined($game_flags->{$flag_name}) &&
261             defined($game_flags->{$flag_name}->[$flag_value])) {
262 0           return $game_flags->{$flag_name}->[$flag_value];
263             } else {
264 0           return $flag_value;
265             }
266             }
267              
268             sub process_segment {
269 0     0 0   my $self = shift;
270 0           my $str = shift;
271              
272 0           my @parts = split(/\x00/, $str);
273 0           my @fields = ();
274 0           foreach (@parts) {
275 0 0         last if $_ eq '';
276 0           s/_.*$//;
277 0           push @fields, $_;
278             }
279              
280 0           my $info = {};
281 0           my $ctr = 0;
282 0           my $cur_item = '';
283 0 0         foreach (splice(@parts, scalar(@fields) + (scalar(@parts) == scalar(@fields) ? 0 : 1))) {
284 0 0         if($ctr % scalar(@fields) == 0) {
285 0           $cur_item = $_;
286 0           $info->{$cur_item}->{$fields[0]} = $cur_item;
287             } else {
288 0           $info->{$cur_item}->{$fields[$ctr % scalar(@fields)]} = $_;
289             }
290 0           $ctr++;
291             }
292              
293 0           return $info;
294             }
295              
296             1;
297              
298             __END__