File Coverage

blib/lib/Games/AssaultCube/Utils.pm
Criterion Covered Total %
statement 36 200 18.0
branch 12 112 10.7
condition 0 9 0.0
subroutine 10 29 34.4
pod 0 25 0.0
total 58 375 15.4


line stmt bran cond sub pod time code
1             # Declare our package
2             package Games::AssaultCube::Utils;
3 2     2   2866 use strict; use warnings;
  2     2   6  
  2         93  
  2         52  
  2         5  
  2         84  
4              
5             # Initialize our version
6 2     2   14 use vars qw( $VERSION );
  2         4  
  2         132  
7             $VERSION = '0.04';
8              
9             # set ourself up for exporting
10 2     2   13 use base qw( Exporter );
  2         6  
  2         8663  
11             our @EXPORT_OK = qw( default_port stripcolors htmlcolors tostr getpongflag get_ac_pingport
12             getint getstring parse_pingresponse parse_masterserverresponse
13             get_gamemode get_gamemode_name get_gamemode_from_name get_gamemode_fullname get_gamemode_from_fullname
14             get_team_from_name get_team_name get_role_from_name get_role_name
15             get_mastermode_from_name get_mastermode_name get_gun_from_name get_gun_name
16             get_disconnect_reason_name get_disconnect_reason_from_name
17             );
18              
19             sub get_ac_pingport {
20 0     0 0 0 my $port = shift;
21 0 0       0 return if ! defined $port;
22              
23             # from protocol.h
24             # #define CUBE_SERVINFO_PORT(serverport) (serverport+1)
25 0         0 return $port + 1;
26             }
27              
28             {
29             # from protocol.h
30             # enum { DISC_NONE = 0, DISC_EOP, DISC_CN, DISC_MKICK, DISC_MBAN, DISC_TAGT, DISC_BANREFUSE, DISC_WRONGPW, DISC_SOPLOGINFAIL, DISC_MAXCLIENTS, DISC_MASTERMODE, DISC_AUTOKICK, DISC_AUTOBAN, DISC_DUP, DISC_NUM };
31             # static const char *disc_reasons[] = { "normal", "end of packet", "client num", "kicked by server operator", "banned by server operator", "tag type", "connection refused due to ban", "wrong password", "failed admin login", "server FULL - maxclients", "server mastermode is \"private\"", "auto kick - did your score drop below the threshold?", "auto ban - did your score drop below the threshold?", "duplicate connection" };
32             my %reason_name = (
33             0 => 'normal',
34             1 => 'end of packet',
35             2 => 'client num',
36             3 => 'kicked by server operator',
37             4 => 'banned by server operator',
38             5 => 'tag type',
39             6 => 'connection refused due to ban',
40             7 => 'wrong password',
41             8 => 'failed admin login',
42             9 => 'server FULL - maxclients',
43             10 => 'server mastermode is "private"',
44             11 => 'auto kick - did your score drop below the threshold?',
45             12 => 'auto ban - did your score drop below the threshold?',
46             13 => 'duplicate connection',
47             );
48             my %name_reason = map { $reason_name{ $_ } => $_ } keys %reason_name;
49              
50             sub get_disconnect_reason_name {
51 0     0 0 0 my $reason = shift;
52 0 0       0 return unless defined $reason;
53 0 0       0 if ( exists $reason_name{ $reason } ) {
54 0         0 return $reason_name{ $reason };
55             } else {
56 0         0 return;
57             }
58             }
59              
60             sub get_disconnect_reason_from_name {
61 0     0 0 0 my $reason = lc( shift );
62 0 0       0 return unless defined $reason;
63 0 0       0 if ( exists $name_reason{ $reason } ) {
64 0         0 return $name_reason{ $reason };
65             } else {
66 0         0 return;
67             }
68             }
69             }
70              
71             sub get_gamemode {
72 0     0 0 0 my $m = shift;
73              
74             # try the fullname first?
75 0         0 my $result = get_gamemode_from_fullname( $m );
76 0 0       0 if ( defined $result ) {
77 0         0 return $result;
78             } else {
79             # try the acronym?
80 0         0 return get_gamemode_from_name( $m );
81             }
82             }
83              
84             {
85             # from entity.h
86             # enum { GUN_KNIFE = 0, GUN_PISTOL, GUN_SHOTGUN, GUN_SUBGUN, GUN_SNIPER, GUN_ASSAULT, GUN_GRENADE, GUN_AKIMBO, NUMGUNS };
87             my %gun_name = (
88             0 => 'KNIFE',
89             1 => 'PISTOL',
90             2 => 'SHOTGUN',
91             3 => 'SUBMACHINE',
92             4 => 'SNIPER',
93             5 => 'ASSAULT',
94             6 => 'GRENADE',
95             7 => 'AKIMBO',
96             );
97             my %name_gun = map { $gun_name{ $_ } => $_ } keys %gun_name;
98              
99             sub get_gun_from_name {
100 0     0 0 0 my $gun = uc( shift );
101 0 0       0 return unless defined $gun;
102 0 0       0 if ( exists $name_gun{ $gun } ) {
103 0         0 return $name_gun{ $gun };
104             } else {
105 0         0 return;
106             }
107             }
108              
109             sub get_gun_name {
110 0     0 0 0 my $gun = shift;
111 0 0       0 return unless defined $gun;
112 0 0       0 if ( exists $gun_name{ $gun } ) {
113 0         0 return $gun_name{ $gun };
114             } else {
115 0         0 return;
116             }
117             }
118             }
119              
120             {
121             # from entity.h
122             #define TEAM_CLA 0
123             #define TEAM_RVSF 1
124             my %team_name = (
125             0 => 'CLA',
126             1 => 'RVSF',
127             2 => 'NONE',
128             );
129             my %name_team = map { $team_name{ $_ } => $_ } keys %team_name;
130              
131             sub get_team_from_name {
132 9     9 0 386 my $team = uc( shift );
133 9 50       19 return unless defined $team;
134 9 50       16 if ( exists $name_team{ $team } ) {
135 9         148 return $name_team{ $team };
136             } else {
137 0         0 return;
138             }
139             }
140              
141             sub get_team_name {
142 0     0 0 0 my $team = shift;
143 0 0       0 return unless defined $team;
144 0 0       0 if ( exists $team_name{ $team } ) {
145 0         0 return $team_name{ $team };
146             } else {
147 0         0 return;
148             }
149             }
150             }
151              
152             {
153             # from entity.h
154             # enum { CR_DEFAULT = 0, CR_ADMIN };
155             my %role_name = (
156             0 => 'DEFAULT',
157             1 => 'ADMIN',
158             );
159             my %name_role = map { $role_name{ $_ } => $_ } keys %role_name;
160              
161             sub get_role_from_name {
162 7     7 0 279274 my $role = uc( shift );
163 7 50       19 return unless defined $role;
164 7 50       18 if ( exists $name_role{ $role } ) {
165 7         46 return $name_role{ $role };
166             } else {
167 0         0 return;
168             }
169             }
170              
171             sub get_role_name {
172 0     0 0 0 my $role = shift;
173 0 0       0 return unless defined $role;
174 0 0       0 if ( exists $role_name{ $role } ) {
175 0         0 return $role_name{ $role };
176             } else {
177 0         0 return;
178             }
179             }
180             }
181              
182             {
183             # from protocol.h
184             # enum { MM_OPEN, MM_PRIVATE, MM_NUM };
185             my %mode_name = (
186             0 => 'OPEN',
187             1 => 'PRIVATE',
188             2 => 'NUM',
189             );
190             my %name_mode = map { $mode_name{ $_ } => $_ } keys %mode_name;
191              
192             sub get_mastermode_from_name {
193 2     2 0 4 my $mode = uc( shift );
194 2 50       5 return unless defined $mode;
195 2 50       4 if ( exists $name_mode{ $mode } ) {
196 2         50 return $name_mode{ $mode };
197             } else {
198 0         0 return;
199             }
200             }
201              
202             sub get_mastermode_name {
203 1     1 0 3 my $mode = shift;
204 1 50       4 return unless defined $mode;
205 1 50       3 if ( exists $mode_name{ $mode } ) {
206 1         7 return $mode_name{ $mode };
207             } else {
208 0         0 return;
209             }
210             }
211             }
212              
213             # parses a HTTP::Response object from the Masterserver
214             sub parse_masterserverresponse {
215 0     0 0 0 my $response = shift;
216              
217             # construct the arrayref of hashrefs of servers, zOMG!
218 0         0 my $result = [];
219              
220             # go through the content, and add server/port to the result
221 0         0 foreach my $l ( split( /[\r\n]+/, $response->content ) ) {
222 0 0       0 if ( ! length $l ) { next }
  0         0  
223              
224             # TODO make this more robust but what the heck!
225 0 0       0 if ( $l =~ /^addserver\s+(\S+)\s+(\d+)\;$/ ) {
226 0         0 my $server = {
227             'ip' => $1,
228             'port' => $2,
229             };
230 0         0 push( @$result, $server );
231             } else {
232 0         0 die "Unknown string in response: $l";
233             }
234             }
235              
236             # all done!
237 0         0 return $result;
238             }
239              
240             # the default AssaultCube server port
241             sub default_port {
242 0     0 0 0 return 28763;
243             }
244              
245             # based on the PHP code, thanks PxL!
246             sub tostr {
247 0     0 0 0 my $hs = shift;
248 0         0 my $rsp = '';
249 0         0 for(my $i = 0; $i < length($hs); $i+=2) {
250 0         0 $rsp .= chr(hex(substr($hs,$i).substr($hs,$i+1)));
251             }
252 0         0 return $rsp;
253             }
254              
255             sub getint {
256 0     0 0 0 my $str = shift;
257              
258             # from protocol.cpp
259             #int getint(ucharbuf &p)
260             #{
261             # int c = (char)p.get();
262             # if(c==-128) { int n = p.get(); n |= char(p.get())<<8; DEBUGVAR(n); return n; }
263             # else if(c==-127) { int n = p.get(); n |= p.get()<<8; n |= p.get()<<16; n |= (p.get()<<24); DEBUGVAR(n); return n; }
264             # else
265             # {
266             # DEBUGVAR(c);
267             # return c;
268             # }
269             #}
270              
271 0 0       0 if ( ! length $$str ) {
272 0         0 return;
273             }
274              
275 0         0 my $c = ord( substr( $$str, 0, 1 ) );
276 0 0       0 if ( $c == 128 ) {
    0          
277 0         0 my $n = ord( substr( $$str, 1, 1 ) );
278 0         0 $n |= ( ord( substr( $$str, 2, 1 ) ) << 8 );
279              
280             # cleanup the string
281 0         0 $$str = substr( $$str, 3 );
282 0         0 return $n;
283             } elsif ( $c == 127 ) {
284 0         0 my $n = ord( substr( $$str, 1, 1 ) );
285 0         0 $n |= ( ord( substr( $$str, 2, 1 ) ) << 8 );
286 0         0 $n |= ( ord( substr( $$str, 3, 1 ) ) << 16 );
287 0         0 $n |= ( ord( substr( $$str, 4, 1 ) ) << 24 );
288              
289             # cleanup the string
290 0         0 $$str = substr( $$str, 5 );
291 0         0 return $n;
292             } else {
293             # cleanup the string
294 0         0 $$str = substr( $$str, 1 );
295 0         0 return $c;
296             }
297             }
298              
299             sub getstring {
300 0     0 0 0 my $str = shift;
301              
302             # from protocol.cpp
303             #void getstring(char *text, ucharbuf &p, int len)
304             #{
305             # char *t = text;
306             # do
307             # {
308             # if(t>=&text[len]) { text[len-1] = 0; return; }
309             # if(!p.remaining()) { *t = 0; return; }
310             # *t = getint(p);
311             # }
312             # while(*t++);
313             # DEBUGVAR(text);
314             #}
315              
316 0 0       0 if ( ! length $$str ) {
317 0         0 return;
318             }
319              
320 0         0 my $ret = '';
321 0         0 my $i = 0;
322 0         0 while ( ord( substr( $$str, $i, 1 ) ) != 0 ) {
323 0         0 $ret .= substr( $$str, $i, 1 );
324 0         0 $i++;
325             }
326              
327             # cleanup the string
328 0         0 $$str = substr( $$str, $i + 1 );
329 0         0 return $ret;
330             }
331              
332             {
333             # from protocol.cpp
334             #const char *modefullnames[] =
335             #{
336             # "demo playback",
337             # "team deathmatch", "coopedit", "deathmatch", "survivor",
338             # "team survivor", "ctf", "pistol frenzy", "bot team deathmatch", "bot deathmatch", "last swiss standing",
339             # "one shot, one kill", "team one shot, one kill", "bot one shot, one kill", "hunt the flag", "team keep the flag", "keep the flag"
340             #};
341              
342             my %mode_name = (
343             0 => 'demo playback',
344             1 => 'team deathmatch',
345             2 => 'coopedit',
346             3 => 'deathmatch',
347             4 => 'survivor',
348             5 => 'team survivor',
349             6 => 'capture the flag', # this is expanded, because I felt "ctf" was silly
350             7 => 'pistol frenzy',
351             8 => 'bot team deathmatch',
352             9 => 'bot deathmatch',
353             10 => 'last swiss standing',
354             11 => 'one shot, one kill',
355             12 => 'team one shot, one kill',
356             13 => 'bot one shot, one kill',
357             14 => 'hunt the flag',
358             15 => 'team keep the flag',
359             16 => 'keep the flag',
360             );
361             my %name_mode = map { $mode_name{ $_ } => $_ } keys %mode_name;
362             $name_mode{'ctf'} = 6; # added so we have full round-trip between perl + AC
363              
364             sub get_gamemode_fullname {
365 0     0 0 0 my $m = shift;
366 0 0       0 return unless defined $m;
367 0 0       0 if ( exists $mode_name{ $m } ) {
368 0         0 return $mode_name{ $m };
369             } else {
370 0         0 return;
371             }
372             }
373              
374             sub get_gamemode_from_fullname {
375 1     1 0 3 my $m = lc( shift );
376 1 50       4 return unless defined $m;
377 1 50       3 if ( exists $name_mode{ $m } ) {
378 1         14 return $name_mode{ $m };
379             } else {
380 0         0 return;
381             }
382             }
383             }
384              
385             {
386             # from protocol.cpp
387             #const char *modeacronymnames[] =
388             #{
389             # "DEMO",
390             # "TDM", "coop", "DM", "SURV", "TSURV", "CTF", "PF", "BTDM", "BDM", "LSS",
391             # "OSOK", "TOSOK", "BOSOK", "HTF", "TKTF", "KTF"
392             #};
393              
394             my %mode_name = (
395             0 => 'DEMO',
396             1 => 'TDM',
397             2 => 'COOP', # uppercased for consistency...
398             3 => 'DM',
399             4 => 'SURV',
400             5 => 'TSURV',
401             6 => 'CTF',
402             7 => 'PF',
403             8 => 'BTDM',
404             9 => 'BDM',
405             10 => 'LSS',
406             11 => 'OSOK',
407             12 => 'TOSOK',
408             13 => 'BOSOK',
409             14 => 'HTF',
410             15 => 'TKTF',
411             16 => 'KTF',
412             );
413             my %name_mode = map { $mode_name{ $_ } => $_ } keys %mode_name;
414              
415             sub get_gamemode_name {
416 0     0 0 0 my $m = shift;
417 0 0       0 return unless defined $m;
418 0 0       0 if ( exists $mode_name{ $m } ) {
419 0         0 return $mode_name{ $m };
420             } else {
421 0         0 return;
422             }
423             }
424              
425             sub get_gamemode_from_name {
426 5     5 0 404 my $m = uc( shift );
427 5 50       20 return unless defined $m;
428 5 50       12 if ( exists $name_mode{ $m } ) {
429 5         64 return $name_mode{ $m };
430             } else {
431 0           return;
432             }
433             }
434             }
435              
436             sub getpongflag {
437 0     0 0   my $pong = shift;
438              
439             # FIXME convert this to proper enums
440              
441             # from protocol.h
442             #enum { PONGFLAG_PASSWORD = 0, PONGFLAG_BANNED, PONGFLAG_BLACKLIST, PONGFLAG_MASTERMODE = 6, PONGFLAG_NUM };
443              
444             # from serverbrowser.cpp
445             # if(si->pongflags > 0)
446             # {
447             # const char *sp = "";
448             # int mm = si->pongflags >> PONGFLAG_MASTERMODE;
449             # if(si->pongflags & (1 << PONGFLAG_BANNED))
450             # sp = "you are banned from this server";
451             # if(si->pongflags & (1 << PONGFLAG_BLACKLIST))
452             # sp = "you are blacklisted on this server";
453             # else if(si->pongflags & (1 << PONGFLAG_PASSWORD))
454             # sp = "this server is password-protected";
455             # else if(mm) sp = mmfullname(mm);
456             # s_sprintf(si->description)("%s \f1(%s)", si->sdesc, sp);
457             # }
458             #
459             # // from protocol.cpp
460             # const char *mmfullnames[] = { "open", "private" };
461              
462 0 0 0       if ( defined $pong and $pong > 0 ) {
463 0           my $mm = $pong >> 6;
464 0 0         if ( $pong & ( 1 << 1 ) ) {
    0          
    0          
465 0           return "you are banned from this server";
466             } elsif ( $pong & ( 1 << 2 ) ) {
467 0           return "you are blacklisted on this server";
468             } elsif ( $pong & ( 1 << 0 ) ) {
469 0           return "this server is password-protected";
470             } else {
471 0 0         if ( $mm ) {
472 0 0         if ( $mm == 1 ) {
    0          
473 0           return "open";
474             } elsif ( $mm == 2 ) {
475 0           return "private";
476             } else {
477 0           return "UNKNOWN";
478             }
479             } else {
480 0           return "UNKNOWN";
481             }
482             }
483             } else {
484 0           return "none";
485             }
486             }
487              
488             sub stripcolors {
489 0     0 0   my $str = shift;
490              
491             # From AC docs/colouredtext.txt
492             # also, look at the PHP code for reference :)
493              
494 0           my $output = '';
495 0           my $foundcolor = 0;
496 0           foreach my $c ( split( //, $str ) ) {
497 0 0         if ( $foundcolor ) {
    0          
498             # skip the damn thing
499 0           $foundcolor = 0;
500             } elsif ( ord( $c ) == 12 ) {
501 0           $foundcolor++
502             } else {
503 0           $output .= $c;
504             }
505             }
506              
507 0           return $output;
508             }
509              
510             {
511             # From AC docs/colouredtext.txt
512             # also, look at the PHP code for reference :)
513             # $html_colors = array(
514             # "<span style='color: #00ee00'>",
515             # "<span style='color: #0000ee'>",
516             # "<span style='color: #f7de12'>",
517             # "<span style='color: #ee0000'>",
518             # "<span style='color: #767676'>",
519             # "<span style='color: #eeeeee'>",
520             # "<span style='color: #824f03'>",
521             # "<span style='color: #9a0000'>"
522             # );
523              
524             my %htmlcolors = (
525             0 => '<font color="#00ee00">',
526             1 => '<font color="#0000ee">',
527             2 => '<font color="#f7de12">',
528             3 => '<font color="#ee0000">',
529             4 => '<font color="#767676">',
530             5 => '<font color="#eeeeee">',
531             6 => '<font color="#824f03">',
532             7 => '<font color="#9a0000">',
533             );
534              
535             sub htmlcolors {
536 0     0 0   my $str = shift;
537              
538 0           my $found = 0;
539 0           my $incolor = 0;
540 0           my $ret = '';
541 0           my @chars = split( //, $str );
542 0           foreach my $i ( 0 .. $#chars ) {
543 0 0         if ( $found ) {
    0          
544 0 0 0       if ( exists $htmlcolors{ $chars[$i] } and defined $chars[$i+1] ) {
545 0           $ret .= $htmlcolors{ $chars[$i] };
546 0           $incolor = 1;
547             } else {
548 0           warn "unknown AC color code: $chars[$i]";
549             }
550 0           $found = 0;
551             } elsif ( ord( $chars[$i] ) == 12 ) {
552 0 0         if ( $incolor ) {
553 0           $ret .= '</font>';
554 0           $incolor = 0;
555             }
556 0           $found = 1;
557             } else {
558 0           $ret .= $chars[$i];
559             }
560             }
561 0 0         if ( $found ) {
562 0           $ret .= '</font>';
563             }
564              
565 0           return $ret;
566             }
567             }
568              
569             sub parse_pingresponse {
570 0     0 0   my $r = shift;
571              
572             # from serverbrowser.cpp
573             #ucharbuf p(ping, len);
574             # si->lastpingmillis = totalmillis;
575             # int pingtm = pingbuf[(getint(p) - 1) % PINGBUFSIZE];
576             # si->ping = pingtm ? totalmillis - pingtm : 9997;
577             # int query = getint(p);
578             # si->protocol = getint(p);
579             # if(si->protocol!=PROTOCOL_VERSION) si->ping = 9998;
580             # si->mode = getint(p);
581             # si->numplayers = getint(p);
582             # si->minremain = getint(p);
583             # getstring(text, p);
584             # filtertext(si->map, text, 1);
585             # getstring(text, p);
586             # filterservdesc(si->sdesc, text);
587             # s_strcpy(si->description, si->sdesc);
588             # si->maxclients = getint(p);
589             # if(p.remaining())
590             # {
591             # si->pongflags = getint(p);
592             # if(p.remaining() && getint(p) == query)
593             # {
594              
595 0           my %data;
596              
597 0           $data{'pingtime'} = getint( \$r );
598 0           $data{'query'} = getint( \$r );
599 0           $data{'protocol'} = getint( \$r );
600 0           $data{'gamemode'} = getint( \$r ) + 1; # for some reason, AC returns mode - 1
601 0           $data{'players'} = getint( \$r );
602 0           $data{'minutes_left'} = getint( \$r );
603 0           $data{'map'} = getstring( \$r );
604 0           $data{'desc'} = getstring( \$r );
605 0           $data{'max_players'} = getint( \$r );
606              
607             # sometimes we don't get pongflags
608 0 0         if ( length( $r ) ) {
609 0           $data{'pong'} = getint( \$r );
610              
611             # sometimes there is no player data...
612 0 0         if ( length( $r ) ) {
613 0           my $query = getint( \$r );
614 0 0         if ( defined $query ) {
615 0 0         if ( $query == 0 ) {
    0          
616             # no extra data
617             } elsif ( $query == 1 ) {
618 0           while ( length( $r ) ) {
619 0           my $player = getstring( \$r );
620 0 0 0       if ( defined $player and $player ne '' ) {
621 0           push( @{ $data{'player_list'} }, $player );
  0            
622             }
623             }
624             } else {
625             # unknown PINGMODE
626 0           die "unknown PINGMODE: $query";
627             }
628             }
629             }
630             }
631              
632 0           return \%data;
633             }
634              
635             1;
636             __END__
637              
638             =for stopwords todo
639              
640             =head1 NAME
641              
642             Games::AssaultCube::Utils - Various utilities for the AssaultCube modules
643              
644             =head1 SYNOPSIS
645              
646             use Games::AssaultCube::Utils qw( default_port );
647             print "The default AssaultCube server port is: " . default_port() . "\n";
648              
649             =head1 ABSTRACT
650              
651             This module holds the various utility functions used in the AssaultCube modules.
652              
653             =head1 DESCRIPTION
654              
655             This module holds the various utility functions used in the AssaultCube modules. Normally you wouldn't
656             need to use this directly.
657              
658             TODO: More documentation about the functions here :)
659              
660             =head1 AUTHOR
661              
662             Apocalypse E<lt>apocal@cpan.orgE<gt>
663              
664             Props goes to Getty and the BS clan for the support!
665              
666             This project is sponsored by L<http://cubestats.net>
667              
668             =head1 COPYRIGHT AND LICENSE
669              
670             Copyright 2009 by Apocalypse
671              
672             This library is free software; you can redistribute it and/or modify
673             it under the same terms as Perl itself.
674              
675             =cut