File Coverage

blib/lib/Games/RolePlay/MapGen/MapQueue.pm
Criterion Covered Total %
statement 30 533 5.6
branch 0 306 0.0
condition 0 71 0.0
subroutine 10 59 16.9
pod 30 36 83.3
total 70 1005 6.9


line stmt bran cond sub pod time code
1             # vi:filetype=perl:
2              
3             package Games::RolePlay::MapGen::MapQueue;
4              
5 6     6   7338 use common::sense;
  6         14  
  6         43  
6 6     6   577 use Carp;
  6         12  
  6         650  
7 6     6   38 use Exporter;
  6         10  
  6         319  
8 6     6   16481 use Math::Trig;
  6         156575  
  6         1668  
9 6     6   7250 use Math::Round;
  6         96407  
  6         588  
10 6     6   69 use List::Util qw(min max);
  6         12  
  6         761  
11 6     6   36 use Storable qw(freeze thaw);
  6         11  
  6         498  
12             use constant {
13 6         1859 LOS_NO => 0,
14             LOS_YES => 1,
15              
16             LOS_NO_COVER => 0,
17             LOS_COVER => 1,
18             LOS_DOUBLE_COVER => 2,
19 6     6   35 };
  6         10  
20              
21             our @ISA = qw(Exporter);
22             our @EXPORT = qw(LOS_NO LOS_YES LOS_NO_COVER LOS_IGNORABLE_COVER LOS_COVER LOS_DOUBLE_COVER);
23              
24             our $LOS_CREATURE_RADIUS = 0.19; # used for double-cover check
25             our $LOS_LHS_BONUS = 0.05_777; # slight advantage for being closer to obstruction
26             our $EXTRUDE_POINTS = 4;
27             our $CLOS_MIN_ANGLE = deg2rad(9); # the minimum angle between our LOS and the closure where we can still tell if there's a door on that wall
28              
29             *_line_of_sight = *_line_of_sight_xs;
30             *_tight_line_of_sight = *_tight_line_of_sight_xs;
31             *_ranged_cover = *_ranged_cover_xs;
32             *_melee_cover = *_melee_cover_xs;
33             *_closure_line_of_sight = *_closure_line_of_sight_xs;
34              
35 6     6   15630 use Memoize qw(memoize flush_cache);
  6         18373  
  6         5723  
36             memoize( _line_of_sight => NORMALIZER => sub { "$_[0] @{$_[1]} @{$_[2]}" } );
37             memoize( _tight_line_of_sight => NORMALIZER => sub { "$_[0] @{$_[1]} @{$_[2]}" } );
38             memoize( _ranged_cover => NORMALIZER => sub { "$_[0] @{$_[1]} @{$_[2]}" } );
39             memoize( _melee_cover => NORMALIZER => sub { "$_[0] @{$_[1]} @{$_[2]}" } );
40             memoize( _ignorable_cover => NORMALIZER => sub { "$_[0] @{$_[1]} @{$_[2]}" } );
41             memoize( _locations_in_line_of_sight => NORMALIZER => sub { "$_[0] @{$_[1]}" } );
42             memoize( _locations_in_range_and_line_of_sight => NORMALIZER => sub { "$_[0] @{$_[1]} $_[2]" } );
43             memoize( _locations_in_path => NORMALIZER => sub { "$_[0] @{$_[1]} @{$_[2]}" } );
44             memoize( _closure_line_of_sight => NORMALIZER => sub { "$_[0] @{$_[1]} @{$_[2]}" } );
45              
46             our @toflush = qw( _line_of_sight _tight_line_of_sight _ranged_cover _melee_cover _ignorable_cover
47             _locations_in_line_of_sight _locations_in_range_and_line_of_sight
48             _locations_in_path _closure_line_of_sight );
49              
50 6     6   65 use Games::RolePlay::MapGen;
  6         14  
  6         144498  
51             require XSLoader; XSLoader::load('Games::RolePlay::MapGen', $Games::RolePlay::MapGen::VERSION);
52              
53             # new {{{
54             sub new {
55 0     0 1   my $class = shift;
56 0           my $the_m = shift;
57 0           my $this = bless { o=>{}, c=>[] }, $class;
58              
59 0 0         croak "where is _the_map?" unless ref $the_m;
60 0           $the_m = $the_m->{_the_map};
61 0           $this->{_the_map} = $the_m;
62              
63 0           $this->{ym} = $#{ $the_m };
  0            
64 0           $this->{xm} = $#{ $the_m->[0] };
  0            
65              
66 0           return $this;
67             }
68             # }}}
69             # retag {{{
70             sub retag {
71 0     0 0   my $this = shift;
72              
73 0           my $tags = {};
74 0           for my $row ( 0 .. $this->{ym} ) {
75 0           for my $col ( 0 .. $this->{xm} ) {
76 0           my $rhs = [ $col, $row ];
77              
78 0 0         for my $o (@{ $this->{c}[ $rhs->[1] ][ $rhs->[0] ] || [] }) {
  0            
79 0           $tags->{"$o"} = $rhs;
80             }
81             }
82             }
83              
84 0           $this->{l} = $tags;
85             }
86             # }}}
87             # flush {{{
88             sub flush {
89 0     0 1   flush_cache($_) for @toflush
90             }
91             # }}}
92              
93             # _check_loc {{{
94             sub _check_loc {
95 0     0     my $this = shift;
96 0           my $loc = shift;
97              
98 0 0         return 0 if @$loc != 2;
99 0 0         return 0 if $loc->[0] < 0;
100 0 0         return 0 if $loc->[1] < 0;
101 0 0         return 0 if $loc->[0] > $this->{xm};
102 0 0         return 0 if $loc->[1] > $this->{ym};
103              
104 0           my $type = $this->{_the_map}[ $loc->[1] ][ $loc->[0] ]{type};
105 0 0         return 0 unless $type; # the wall type is
106              
107 0           return $loc;
108             }
109             # }}}
110             # _od_segments {{{
111             sub _od_segments {
112 0     0     my $this = shift;
113 0           my ($lhs, $rhs) = @_;
114              
115             ## DEBUG ## warn "SET\n<@$lhs> <@$rhs>\n";
116              
117 0           my @X = sort {$a<=>$b} ($lhs->[0], $rhs->[0]); @X = ($X[0] .. $X[1]);
  0            
  0            
118 0           my @Y = sort {$a<=>$b} ($lhs->[1], $rhs->[1]); @Y = ($Y[0] .. $Y[1]);
  0            
  0            
119              
120 0 0         my $x_dir = ($lhs->[0] < $rhs->[0] ? "e" : "w");
121 0 0         my $y_dir = ($lhs->[1] < $rhs->[1] ? "s" : "n");
122              
123 0           my @od_segments = (); # the solid line segments we might have to pass through
124 0           for my $x (@X[0 .. $#X]) {
125 0           for my $y (@Y[0 .. $#Y]) {
126 0           my $x_od = $this->{_the_map}[ $y ][ $x ]{od}{ $x_dir };
127 0           my $y_od = $this->{_the_map}[ $y ][ $x ]{od}{ $y_dir };
128              
129 0           for( $x_od, $y_od ) {
130 0 0         $_ = $_->{'open'} if ref $_;
131             }
132              
133 0 0 0       unless( $x_od or $x == ($x_dir eq "e" ? $X[$#X]:$X[0]) ) {
    0          
134 0 0         if( $x_dir eq "e" ) { push @od_segments, [[ $x+1, $y ] => [$x+1, $y+1]] }
  0            
135 0           else { push @od_segments, [[ $x, $y ] => [$x, $y+1]] }
136             }
137              
138 0 0 0       unless( $y_od or $y == ($y_dir eq "s" ? $Y[$#Y]:$Y[0]) ) {
    0          
139 0 0         if( $y_dir eq "s" ) { push @od_segments, [[ $x, $y+1 ] => [$x+1, $y+1]] }
  0            
140 0           else { push @od_segments, [[ $x, $y ] => [$x+1, $y ]] }
141             }
142             }
143             }
144              
145             ## DEBUG ## warn "(@{$_->[0]})->(@{$_->[1]})\n" for @od_segments;
146             ## DEBUG ## warn "DONE\n";
147              
148 0           return @od_segments;
149             }
150             # }}}
151             # _extrude_point {{{
152             sub _extrude_point {
153             # extrude a point into a tile or a sub-tile
154 0     0     my $this = shift;
155 0           my $point = shift;
156 0           my $use_ocr = shift; # use our creature radius
157 0           my $use_lhs = shift; # use our lhs bonus
158              
159 0 0 0       die "EXTRUDE_POINTS=$EXTRUDE_POINTS must be an even integer" unless $EXTRUDE_POINTS >= 2 and not $EXTRUDE_POINTS =~ m/\./
      0        
160             and not $EXTRUDE_POINTS & 1; # needed for closure_line_of_sight
161              
162 0 0         my $s = ($use_ocr ? 0.50-$LOS_CREATURE_RADIUS-($use_lhs ? $LOS_LHS_BONUS : 0) : 0.0001);
    0          
163 0 0         my $e = ($use_ocr ? 0.50+$LOS_CREATURE_RADIUS+($use_lhs ? $LOS_LHS_BONUS : 0) : 0.9999);
    0          
164 0           my $i = ( abs($s-$e) / ($EXTRUDE_POINTS-1) );
165              
166 0           my @r = (
167             [$point->[0] + $s, $point->[1] + $s],
168             [$point->[0] + $e, $point->[1] + $s],
169             [$point->[0] + $s, $point->[1] + $e],
170             [$point->[0] + $e, $point->[1] + $e],
171             );
172              
173             ## DEBUG ## return @r; # psh> require "MapGen/MapQueue.pm"; d[ Games::RolePlay::MapGen::MapQueue->_extrude_point([5,5]) ]
174              
175 0           my $c = $s+$i;
176 0           while( $c < $e ) {
177 0           push @r,
178             [$point->[0] + $c, $point->[1] + $s],
179             [$point->[0] + $s, $point->[1] + $c],
180             [$point->[0] + $c, $point->[1] + $e],
181             [$point->[0] + $e, $point->[1] + $c],
182 0           ;$c += $i;
183             }
184              
185             # use Data::Dumper; $Data::Dumper::Indent = $Data::Dumper::Sortkeys = 0;
186             # warn Dumper([$s, $e, $i, \@r]);
187              
188 0           my %h;
189 0           return grep {my $x = not $h{"@$_"}; $h{"@$_"}=1; $x} @r;
  0            
  0            
  0            
190             }
191             # }}}
192             # _tight_line_of_sight_xs {{{
193             sub _tight_line_of_sight_xs {
194             my $this = shift;
195             my ($lhs, $rhs) = @_;
196              
197             return LOS_YES if "@$lhs" eq "@$rhs";
198              
199             my @ods = $this->_od_segments(@_);
200             my @lhs = $this->_extrude_point( $lhs, 1,1 ); # ocr,lhs
201             my @rhs = $this->_extrude_point( $rhs, 1,0 );
202              
203             return LOS_YES if &Games::RolePlay::MapGen::MapQueue::any_any_los_loop(\@lhs, \@rhs, \@ods);
204             return LOS_NO;
205             }
206             # }}}
207             # _tight_line_of_sight_pl {{{
208             sub _tight_line_of_sight_pl {
209 0     0     my $this = shift;
210 0           my ($lhs, $rhs) = @_;
211              
212 0 0         return LOS_YES if "@$lhs" eq "@$rhs";
213              
214 0           my @od_segments = $this->_od_segments(@_);
215              
216 0           my @lhs = $this->_extrude_point( $lhs, 1,1 ); # ocr,lhs
217 0           my @rhs = $this->_extrude_point( $rhs, 1,0 );
218              
219             ##---------------- LOS CALC
220 0           my $line = 0;
221              
222             ## DEBUG ## warn "SET\n";
223             ## DEBUG ## warn "\@target: <@$rhs>\n";
224             ## DEBUG ## warn "wall: (@{$_->[0]})->(@{$_->[1]})\n" for @od_segments;
225             LOS_CHECK:
226 0           for my $l (@lhs) {
227 0           for my $r (@rhs) {
228 0           my $this_line = 1;
229              
230             OD_CHECK:
231 0           for my $od_segment (@od_segments) {
232 0 0         if( $this->_line_segments_intersect( (map {@$_} @$od_segment) => (@$l=>@$r) ) ) {
  0            
233 0           $this_line = 0;
234              
235 0           last OD_CHECK;
236             }
237             }
238              
239 0 0         if( $this_line ) {
240             ## DEBUG ## warn "LOS: (@$l)->(@$r)\n";
241 0           $line = 1;
242 0           last LOS_CHECK;
243             }
244             }}
245             ## DEBUG ## warn "DONE\n";
246              
247 0 0         return LOS_NO unless $line;
248 0           return LOS_YES; # cover needs to be double checked
249             }
250             # }}}
251             # _line_of_sight_xs {{{
252             sub _line_of_sight_xs {
253             my $this = shift;
254             my ($lhs, $rhs) = @_;
255              
256             return LOS_YES if "@$lhs" eq "@$rhs";
257              
258             my @ods = $this->_od_segments(@_);
259             my @lhs = $this->_extrude_point( $lhs, 0,0 ); # ocr,lhs
260             my @rhs = $this->_extrude_point( $rhs, 0,0 );
261              
262             return LOS_YES if &Games::RolePlay::MapGen::MapQueue::any_any_los_loop(\@lhs, \@rhs, \@ods);
263             return LOS_NO;
264             }
265             # }}}
266             # _line_of_sight_pl {{{
267             sub _line_of_sight_pl {
268 0     0     my $this = shift;
269 0           my ($lhs, $rhs) = @_;
270              
271 0 0         return LOS_YES if "@$lhs" eq "@$rhs";
272              
273 0           my @od_segments = $this->_od_segments(@_);
274              
275 0           my @lhs = $this->_extrude_point( $lhs, 0,0 ); # ocr,lhs
276 0           my @rhs = $this->_extrude_point( $rhs, 0,0 );
277              
278             # warn "LHS: " . join(" ", map(sprintf('<%9.6f, %9.6f>', @$_), @lhs));
279             # warn "RHS: " . join(" ", map(sprintf('[%9.6f, %9.6f]', @$_), @rhs));
280             # warn "ODS: " . join(" ", map(sprintf('(%9.6f, %9.6f)->(%9.6f, %9.6f)', @{$_->[0]}, @{$_->[1]}), @od_segments));
281              
282 0           my $line = 0;
283              
284             ## DEBUG ## warn "---------- LOS @$lhs => @$rhs\n";
285              
286             LOS_CHECK:
287 0           for my $l (@lhs) {
288 0           for my $r (@rhs) {
289 0           my $this_line = 1;
290              
291             OD_CHECK:
292 0           for my $od_segment (@od_segments) {
293 0 0         if( $this->_line_segments_intersect( (map {@$_} @$od_segment) => (@$l=>@$r) ) ) {
  0            
294 0           $this_line = 0;
295              
296 0           last OD_CHECK;
297             }
298             }
299              
300 0 0         if( $this_line ) {
301             ## DEBUG ## warn "\tfound: (@$l)->(@$r)\n";
302 0           $line = 1;
303 0           last LOS_CHECK;
304             }
305             ## DEBUG ## else { warn "\treject: (@$l)->(@$r)\n"; }
306             }}
307              
308 0 0         return LOS_NO unless $line;
309 0           return LOS_YES; # cover needs to be double checked
310             }
311             # }}}
312             # _ranged_cover_pl {{{
313             sub _ranged_cover_pl {
314 0     0     my $this = shift;
315 0           my ($lhs, $rhs) = @_;
316              
317 0 0         return LOS_NO_COVER if "@$lhs" eq "@$rhs";
318              
319 0           my @od_segments = $this->_od_segments(@_);
320              
321 0           my @lhs = $this->_extrude_point( $lhs, 0,0 ); # ocr,lhs
322 0           my @rhs = $this->_extrude_point( $rhs, 0,0 );
323            
324 0           for my $l (@lhs) {
325 0           my $cover = 0;
326              
327             ## DEBUG ## warn "SET\n";
328             ## DEBUG ## warn "<@$lhs> <@$rhs>\n";
329              
330 0           RCRHS: for my $r (@rhs) {
331 0           for my $od_segment (@od_segments) {
332 0 0         if( $this->_line_segments_intersect( (map {@$_} @$od_segment) => (@$l=>@$r) ) ) {
  0            
333             ## DEBUG ## warn "SET\n<@$lhs> <@$rhs>\n";
334             ## DEBUG ## warn "(@{$od_segment->[0]})->(@{$od_segment->[1]}) (@$l)->(@$r)\n";
335             ## DEBUG ## warn "DONE\n";
336 0           $cover = 1;
337 0           last RCRHS;
338             }
339             }
340             }
341              
342             ## DEBUG ## warn "DONE\n";
343              
344             # for ranged cover, if we can find even one lhs corner that can see all the rhs corners
345             # then we return LOS_NO_COVER;
346 0 0         unless( $cover ) {
347             ## DEBUG ## warn "\e[32m here(@$l) \e[m";
348             # NOTE: this cover-upgrade _not_ d20 rules:
349 0 0         return LOS_COVER unless $this->_tight_line_of_sight( $lhs => $rhs );
350 0           return LOS_NO_COVER;
351             }
352             }
353              
354             ## DEBUG ## warn "\e[32m here(---) \e[m";
355              
356             # NOTE: this cover-upgrade is _not_ d20 rules:
357 0 0         return LOS_DOUBLE_COVER unless $this->_tight_line_of_sight( $lhs => $rhs );
358 0           return LOS_COVER;
359             }
360             # }}}
361             # _ranged_cover_xs {{{
362             sub _ranged_cover_xs {
363             my $this = shift;
364             my ($lhs, $rhs) = @_;
365              
366             return LOS_NO_COVER if "@$lhs" eq "@$rhs";
367              
368             my @ods = $this->_od_segments(@_);
369             my @lhs = $this->_extrude_point( $lhs, 0,0 ); # ocr,lhs
370             my @rhs = $this->_extrude_point( $rhs, 0,0 );
371              
372             if( &Games::RolePlay::MapGen::MapQueue::any_all_los_loop(\@lhs, \@rhs, \@ods) ) {
373             ## DEBUG ## warn "\e[31m here(@@@) \e[m";
374             return LOS_COVER unless $this->_tight_line_of_sight( $lhs => $rhs );
375             return LOS_NO_COVER;
376             }
377              
378             ## DEBUG ## warn "\e[31m here(---) \e[m";
379              
380             return LOS_DOUBLE_COVER unless $this->_tight_line_of_sight( $lhs => $rhs );
381             return LOS_COVER;
382             }
383             # }}}
384             # _melee_cover_pl {{{
385             sub _melee_cover_pl {
386 0     0     my $this = shift;
387 0           my ($lhs, $rhs) = @_;
388              
389             # NOTE: Let the caller figure this out? Different creatures have different
390             # reach and reach weapons should be using ranged_cover() anyway. On the
391             # other hand, this map-logic doesn't even begin to consider creatures that
392             # take up more than one tile...
393              
394 0 0         return LOS_NO_COVER if abs($lhs->[0]-$rhs->[0]) > 1;
395 0 0         return LOS_NO_COVER if abs($lhs->[1]-$rhs->[1]) > 1;
396              
397             # end_NOTE
398              
399 0           my @od_segments = $this->_od_segments(@_);
400              
401 0           my @lhs = $this->_extrude_point( $lhs, 0,0 ); # ocr,lhs
402 0           my @rhs = $this->_extrude_point( $rhs, 0,0 );
403            
404 0           for my $l (@lhs) {
405 0           for my $r (@rhs) {
406 0           my $cover = 0;
407              
408 0           for my $od_segment (@od_segments) {
409 0 0         if( $this->_line_segments_intersect( (map {@$_} @$od_segment) => (@$l=>@$r) ) ) {
  0            
410             # This short circuits quickly half the time (on average). If
411             # there's cover from any corner it counds as melee cover!
412 0           return LOS_COVER;
413             }
414             }
415             }}
416              
417 0           return LOS_NO_COVER;
418             }
419             # }}}
420             # _melee_cover_xs {{{
421             sub _melee_cover_xs {
422             my $this = shift;
423             my ($lhs, $rhs) = @_;
424              
425             return LOS_NO_COVER if abs($lhs->[0]-$rhs->[0]) > 1;
426             return LOS_NO_COVER if abs($lhs->[1]-$rhs->[1]) > 1;
427              
428             my @ods = $this->_od_segments(@_);
429             my @lhs = $this->_extrude_point( $lhs, 0,0 ); # ocr,lhs
430             my @rhs = $this->_extrude_point( $rhs, 0,0 );
431              
432             return LOS_COVER
433             if &Games::RolePlay::MapGen::MapQueue::any_any_intersect_loop(\@lhs, \@rhs, \@ods);
434              
435             return LOS_NO_COVER;
436             }
437             # }}}
438             # _closure_line_of_sight_pl {{{
439             sub _closure_line_of_sight_pl {
440 0     0     my $this = shift;
441 0           my $lhs = shift;
442 0           my $rhsd = shift;
443              
444 0           my $s = (0.0001);
445 0           my $e = (0.9999);
446 0           my $i = (abs($s-$e) / ($EXTRUDE_POINTS-1));
447              
448             # NOTE: We build a row of points just "this side" of the door using (@c,$b)
449             # for n/s doors or ($b,@c) for e/w ones. When we're done, there's a row of
450             # points in the @rhs, built from @c and $b.
451              
452 0           my @c = ($s); $c[@c] = $c[$#c] + $i while $c[$#c] < $e;
  0            
453 0           my $b;
454              
455 0 0         if( $rhsd->[2] eq "n" ) { $b = $rhsd->[1] + ($lhs->[1]>=$rhsd->[1] ? 0.01 : -0.01) } # slightly more or less than 0
  0 0          
    0          
    0          
    0          
456 0 0         elsif( $rhsd->[2] eq "s" ) { $b = $rhsd->[1] + ($lhs->[1]<=$rhsd->[1] ? 0.99 : 1.01) } # slightly more or less than 1
457 0 0         elsif( $rhsd->[2] eq "e" ) { $b = $rhsd->[0] + ($lhs->[0]<=$rhsd->[0] ? 0.99 : 1.01) }
458 0 0         elsif( $rhsd->[2] eq "w" ) { $b = $rhsd->[0] + ($lhs->[0]>=$rhsd->[0] ? 0.01 : -0.01) }
459              
460 0           my @rhs;
461 0 0         if( $rhsd->[2] eq "n" ) { @rhs = map {[ $rhsd->[0]+$_, $b ]} @c }
  0 0          
  0 0          
    0          
462 0           elsif( $rhsd->[2] eq "s" ) { @rhs = map {[ $rhsd->[0]+$_, $b ]} @c }
  0            
463 0           elsif( $rhsd->[2] eq "e" ) { @rhs = map {[ $b, $rhsd->[1]+$_ ]} @c }
  0            
464 0           elsif( $rhsd->[2] eq "w" ) { @rhs = map {[ $b, $rhsd->[1]+$_ ]} @c }
  0            
465              
466 0           my $v = [ $rhs[-1][0]-$rhs[0][0], $rhs[-1][1]-$rhs[0][1] ]; # vector @origin describing the line-segment named @rhs
467 0           my $mv = sqrt( $v->[0]**2 + $v->[1]**2 );
468 0           $v = [ map { $_/$mv } @$v ]; # unit vector describing the line-segment named @rhs
  0            
469              
470 0           my $c = [ $rhsd->[0] + $v->[0]/2, $rhsd->[1] + $v->[1]/2 ]; # center of the line-segment named $rhsd
471 0 0         $c->[0] ++ if $rhsd->[2] eq "e"; # which does require some minor correction
472 0 0         $c->[1] ++ if $rhsd->[2] eq "s";
473              
474 0           my @od_segments = $this->_od_segments($lhs, [$rhs[0][0],$rhs[0][1]]); # line segments possibly in the way
475              
476 0           my @lhs =
477             grep {
478 0           my $l = $_;
479 0           my $ok = 1;
480 0           for my $r (@rhs) {
481 0           for my $od_segment (@od_segments) {
482 0 0         if( my @i = $this->_line_segments_intersect( (map {@$_} @$od_segment) => (@$l=>@$r) ) ) {
  0            
483 0           $ok = 0;
484 0           last;
485             }
486             }
487             }
488              
489             $ok
490              
491 0           } grep {
492 0           my $ab;
493 0           my $od = $this->{_the_map}[ $rhsd->[1] ][ $rhsd->[0] ]{od}{ $rhsd->[2] };
494 0           my $rf = ref $od;
495 0 0 0       if( ($od and not $rf) or ($rf and $od->{'open'}) ) {
      0        
      0        
496 0           $ab = 360;
497              
498             } else {
499 0           my $u = [ $c->[0]-$_->[0], $c->[1]-$_->[1] ]; # the line-segment from the $_ to the center of the closure
500 0           my $mu = sqrt( $u->[0]**2 + $u->[1]**2 );
501 0           $u = [ map { abs $_/$mu } @$u ]; # unit vector of $u -- er, the totally positive version anyway
  0            
502              
503             # We wish to exclude points that are within a certain arc.
504             # Anything within $CLOS_MIN_ANGLE degrees of the wall plane
505             # we're searching is defined to be an akward search angle
506              
507 0           my $cab = $v->[0]*$u->[0] + $v->[1]*$u->[1];
508              
509 0           $ab = acos( $cab );
510             }
511              
512             # $ab, hopefully, contains the angle between the vectors
513 0           $ab >= $CLOS_MIN_ANGLE;
514             }
515              
516             # All the points around the edge of the source tile. We do not need to
517             # worry about any lhs being in the same line segment as the rhs since
518             # none of them should be $c and all of them will have too small of an
519             # angle between -- this assumes EXTRUDE_POINTS is even, which is now
520             # enforced in _ex_p
521              
522             ($this->_extrude_point( $lhs, 0,0 ), [$lhs->[0]+0.5,$lhs->[1]+0.5]);
523              
524 0 0         my $min = (@lhs ? min map { my $l = $_; (max map { sqrt(($l->[0]-$_->[0])**2 + ($l->[1]-$_->[1])**2) } @rhs) } @lhs : 0);
  0            
  0            
  0            
525 0           return $min;
526             }
527             # }}}
528             # _closure_line_of_sight_xs {{{
529             sub _closure_line_of_sight_xs {
530             my $this = shift;
531             my $lhs = shift;
532             my $rhsd = shift;
533              
534             my $s = (0.0001);
535             my $e = (0.9999);
536             my $i = (abs($s-$e) / ($EXTRUDE_POINTS-1));
537              
538             # NOTE: We build a row of points just "this side" of the door using (@c,$b)
539             # for n/s doors or ($b,@c) for e/w ones. When we're done, there's a row of
540             # points in the @rhs, built from @c and $b.
541              
542             my @c = ($s); $c[@c] = $c[$#c] + $i while $c[$#c] < $e;
543             my $b;
544              
545             if( $rhsd->[2] eq "n" ) { $b = $rhsd->[1] + ($lhs->[1]>=$rhsd->[1] ? 0.01 : -0.01) } # slightly more or less than 0
546             elsif( $rhsd->[2] eq "s" ) { $b = $rhsd->[1] + ($lhs->[1]<=$rhsd->[1] ? 0.99 : 1.01) } # slightly more or less than 1
547             elsif( $rhsd->[2] eq "e" ) { $b = $rhsd->[0] + ($lhs->[0]<=$rhsd->[0] ? 0.99 : 1.01) }
548             elsif( $rhsd->[2] eq "w" ) { $b = $rhsd->[0] + ($lhs->[0]>=$rhsd->[0] ? 0.01 : -0.01) }
549              
550             my @rhs; # we don't know what the rhs is until we figure out where the door is in relation to the $lhs
551             if( $rhsd->[2] eq "n" ) { @rhs = map {[ $rhsd->[0]+$_, $b ]} @c }
552             elsif( $rhsd->[2] eq "s" ) { @rhs = map {[ $rhsd->[0]+$_, $b ]} @c }
553             elsif( $rhsd->[2] eq "e" ) { @rhs = map {[ $b, $rhsd->[1]+$_ ]} @c }
554             elsif( $rhsd->[2] eq "w" ) { @rhs = map {[ $b, $rhsd->[1]+$_ ]} @c }
555              
556             my $v = [ $rhs[-1][0]-$rhs[0][0], $rhs[-1][1]-$rhs[0][1] ]; # vector @origin describing the line-segment named @rhs
557             my $mv = sqrt( $v->[0]**2 + $v->[1]**2 );
558             $v = [ map { $_/$mv } @$v ]; # unit vector describing the line-segment named @rhs
559              
560             my $c = [ $rhsd->[0] + $v->[0]/2, $rhsd->[1] + $v->[1]/2 ]; # center of the line-segment named $rhsd
561             $c->[0] ++ if $rhsd->[2] eq "e"; # which does require some minor correction
562             $c->[1] ++ if $rhsd->[2] eq "s";
563              
564             my @ods = $this->_od_segments($lhs, [$rhs[0][0],$rhs[0][1]]); # line segments possibly in the way
565              
566             my @lhs = grep { &Games::RolePlay::MapGen::MapQueue::any_all_los_loop([$_], \@rhs, \@ods) }
567             grep {
568             my $ab;
569             my $od = $this->{_the_map}[ $rhsd->[1] ][ $rhsd->[0] ]{od}{ $rhsd->[2] };
570             my $rf = ref $od;
571             if( ($od and not $rf) or ($rf and $od->{'open'}) ) {
572             $ab = 360;
573              
574             } else {
575             my $u = [ $c->[0]-$_->[0], $c->[1]-$_->[1] ]; # the line-segment from the $_ to the center of the closure
576             my $mu = sqrt( $u->[0]**2 + $u->[1]**2 );
577             $u = [ map { abs $_/$mu } @$u ]; # unit vector of $u -- er, the totally positive version anyway
578              
579             # We wish to exclude points that are within a certain arc.
580             # Anything within $CLOS_MIN_ANGLE degrees of the wall plane
581             # we're searching is defined to be an akward search angle
582              
583             my $cab = $v->[0]*$u->[0] + $v->[1]*$u->[1];
584              
585             $ab = acos( $cab );
586             }
587              
588             # $ab, hopefully, contains the angle between the vectors
589             $ab >= $CLOS_MIN_ANGLE;
590             }
591              
592             # All the points around the edge of the source tile. We do not need to
593             # worry about any lhs being in the same line segment as the rhs since
594             # none of them should be $c and all of them will have too small of an
595             # angle between -- this assumes EXTRUDE_POINTS is even, which is now
596             # enforced in _ex_p
597              
598             ($this->_extrude_point( $lhs, 0,0 ), [$lhs->[0]+0.5,$lhs->[1]+0.5]);
599              
600             my $min = (@lhs ? min map { my $l = $_; (max map { sqrt(($l->[0]-$_->[0])**2 + ($l->[1]-$_->[1])**2) } @rhs) } @lhs : 0);
601             return $min;
602             }
603             # }}}
604             # _mxb_of_sight (returns m and b of y=mx+b fame) {{{
605             sub _mxb_of_sight {
606 0     0     my $this = shift;
607 0           my ($lhs, $rhs) = @_;
608              
609 0 0         return if "@$lhs" eq "@$rhs";
610              
611             ## DEBUG ## warn "---------- MXB @$lhs => @$rhs\n";
612              
613 0           my @od_segments = $this->_od_segments(@_);
614              
615 0           my @lhs = $this->_extrude_point( $lhs, 0,0 ); # ocr,lh
616 0           my @rhs = $this->_extrude_point( $rhs, 0,0 );
617              
618 0           for my $l (sort { $this->_ldistance($a=>$rhs) <=> $this->_ldistance($b=>$rhs) } @lhs) {
  0            
619 0           for my $r (sort { $this->_ldistance($a=>$l) <=> $this->_ldistance($b=>$l) } @rhs) {
  0            
620 0           my $this_line = 1;
621              
622             OD_CHECK:
623 0           for my $od_segment (@od_segments) {
624 0 0         if( $this->_line_segments_intersect( (map {@$_} @$od_segment) => (@$l=>@$r) ) ) {
  0            
625 0           $this_line = 0;
626              
627 0           last OD_CHECK;
628             }
629             }
630              
631 0 0         if( $this_line ) {
632 0           my $d = ($r->[0]-$l->[0]);
633 0 0         my $m = ($d != 0 ? ( ($r->[1]-$l->[1]) / $d ) : undef );
634 0 0         my $b = (defined $m ? ($l->[1] - ($m*$l->[0])) : 0);
635              
636             ## DEBUG ## warn "\tfound: (@$l)->(@$r)\n";
637              
638 0           return ($m, $b, $l, $r);
639             }
640             ## DEBUG ## else { warn "\treject: (@$l)->(@$r)\n"; }
641             }}
642              
643 0           return;
644             }
645             # }}}
646             # _ignorable_cover {{{
647             sub _ignorable_cover {
648             my $this = shift;
649             my ($lhs, $rhs) = @_;
650              
651             warn "ignorable cover isn't actually calculated";
652              
653             return 0;
654             }
655             # }}}
656             # _ldistance {{{
657             sub _ldistance {
658 0     0     my $this = shift;
659 0           my ($lhs, $rhs) = @_;
660              
661 0           return sqrt ( (($lhs->[0]-$rhs->[0]) ** 2) + (($lhs->[1]-$rhs->[1]) ** 2) );
662             }
663             # }}}
664             # _locations_in_line_of_sight {{{
665             sub _locations_in_line_of_sight {
666             my $this = shift;
667             my $init = shift;
668             my @loc = ();
669             my @new = ($init);
670              
671             my %checked = ( "@$init" => 1 );
672             while( @new ) {
673             my @very_new = ();
674              
675             for my $i (@new) {
676             for my $j ( [$i->[0]+1, $i->[1]], [$i->[0]-1, $i->[1]], [$i->[0], $i->[1]+1], [$i->[0], $i->[1]-1] ) {
677             next if $checked{"@$j"}; $checked{"@$j"} = 1;
678             next unless $this->_check_loc($j);
679              
680             push @very_new, $j if $this->_line_of_sight( $init => $j );
681             }
682             }
683              
684             push @loc, @new;
685             @new = @very_new;
686             }
687              
688             return @loc;
689             }
690             # }}}
691             # _locations_in_range_and_line_of_sight {{{
692             sub _locations_in_range_and_line_of_sight {
693             my $this = shift;
694             my $init = shift;
695             my $range = shift;
696             my @loc = ();
697             my @new = ($init);
698              
699             my %checked = ( "@$init" => 1 );
700             while( @new ) {
701             my @very_new = ();
702              
703             for my $i (@new) {
704             for my $j ( [$i->[0]+1, $i->[1]], [$i->[0]-1, $i->[1]], [$i->[0], $i->[1]+1], [$i->[0], $i->[1]-1] ) {
705             next if $checked{"@$j"}; $checked{"@$j"} = 1;
706             next unless $this->_check_loc($j);
707             next unless sqrt( ($init->[0]-$j->[0])**2 + ($init->[1]-$j->[1])**2) <= $range;
708              
709             push @very_new, $j if $this->_line_of_sight( $init => $j );
710             }
711             }
712              
713             push @loc, @new;
714             @new = @very_new;
715             }
716              
717             return @loc;
718             }
719             # }}}
720             # _objs_at_location {{{
721             sub _objs_at_location {
722 0     0     my $this = shift;
723 0           my $loc = shift;
724 0 0         my @itm = @{ $this->{c}[ $loc->[1] ][ $loc->[0] ] || [] };
  0            
725              
726 0           return @itm; # this is a copy, so it's silly to use wantarray...
727             }
728             # }}}
729             # _locations_in_path {{{
730             sub _locations_in_path {
731             my $this = shift;
732             my $lhs = shift;
733             my $rhs = shift;
734             my @path = ();
735              
736             return ([@$lhs],[@$rhs]) if "@$lhs" eq "@$rhs";
737              
738             my ($m, $b, $p0, $p1) = $this->_mxb_of_sight($lhs => $rhs);
739              
740             ## DEBUG ## warn "m=$m; b=$b; p0=(@$p0); p1=(@$p1)";
741              
742             my $ranger = sub {
743             my ($l, $r) = @_;
744              
745             return ( $l<$r ? ($l+1 .. $r-1) : (reverse ($r+1 .. $l-1)) );
746             };
747              
748             push @path, [@$lhs];
749              
750             if( not defined $m ) {
751             for my $y ( $ranger->($lhs->[1] => $rhs->[1]) ) {
752             my $x = $lhs->[0]; # == $rhs->[0]
753              
754             push @path, [$x,$y];
755             }
756              
757             } elsif( (abs $m) > 1 ) {
758             for my $y ( $ranger->($lhs->[1] => $rhs->[1]) ) {
759             my $z = (($y+0.5)-$b)/$m;
760             my $x = round($z-0.5);
761              
762             push @path, [$x,$y];
763             }
764             } elsif( $m == 0 ) {
765             for my $x ( $ranger->($lhs->[0] => $rhs->[0]) ) {
766             my $y = round($b);
767              
768             push @path, [$x,$y];
769             }
770              
771             } else {
772             for my $x ( $ranger->($lhs->[0] => $rhs->[0]) ) {
773             my $z = ($m * ($x+0.5)) + $b;
774             my $y = round($z-0.5);
775              
776             push @path, [$x,$y];
777             }
778             }
779              
780             push @path, [@$rhs];
781              
782             for my $list ([+1, reverse 0 .. $#path-1], [-1, 1 .. $#path]) { my $ni = shift @$list;
783             for my $i (@$list) {
784             my $changes = 0;
785              
786             for my $j (0,1) {
787             my $A = $path[$i][$j];
788             my $d = $path[$i+$ni][$j] - $A;
789             my $md = abs $d;
790             if( $md > 1 ) {
791             $A += $d/$md;
792              
793             ## DEBUG ## warn (($j==0 ? "X":"Y") . "-CHANGE($i,$j)::(@{$path[$i]})[$j] = $A\n");
794              
795             $path[$i][$j] = $A;
796             $changes ++;
797             }
798              
799             ## DEBUG ## else { warn (($j==0 ? "X":"Y") . "-!NO!CHANGE($i,$j)::(@{$path[$i]})[$j] = $A; md=$md; d=$d\n"); }
800             }
801              
802             last unless $changes;
803             }}
804              
805             DIAG_ORDEAL: {
806             my $map = $this->{_the_map};
807             for my $i ( 0 .. $#path-1 ) {
808             my $j = $i + 1;
809             my ($lhs, $rhs) = ($path[$i], $path[$j]);
810              
811             if( $lhs->[0] != $rhs->[0] and $lhs->[1] != $rhs->[1] ) {
812             # NOTE: a diagonal move is illegal if there's a "corner" in the way phb p. 147
813              
814             LHS_DIAG_VIOLATION: {
815             my $lod = $map->[ $lhs->[1] ][ $lhs->[0] ]{od};
816             my $xdir = ($lhs->[0]<$rhs->[0] ? 'e':'w'); my $xo = $lod->{$xdir}; $xo = 1 if ref $xo and $xo->{'open'};
817             my $ydir = ($lhs->[1]<$rhs->[1] ? 's':'n'); my $yo = $lod->{$ydir}; $yo = 1 if ref $yo and $yo->{'open'};
818              
819             if( not $yo ) {
820             if( $i == 0 or ($path[$i-1][0] != $lhs->[0]) ) {
821             splice @path, $j, 0, [ $rhs->[0], $lhs->[1] ]; # 0-width inserts at $j
822             redo DIAG_ORDEAL;
823              
824             } else {
825             $lhs->[0] = $rhs->[0];
826             }
827              
828             } elsif( not $xo ) {
829             if( $i == 0 or ($path[$i-1][1] != $lhs->[1]) ) {
830             splice @path, $j, 0, [ $lhs->[0], $rhs->[1] ]; # 0-width inserts at $j
831             redo DIAG_ORDEAL;
832              
833             } else {
834             $lhs->[1] = $rhs->[1];
835             }
836             }
837             }
838              
839             RHS_DIAG_VIOLATION: {
840             my $lod = $map->[ $rhs->[1] ][ $rhs->[0] ]{od};
841             my $xdir = ($lhs->[0]<$rhs->[0] ? 'w':'e'); my $xo = $lod->{$xdir}; $xo = 1 if ref $xo and $xo->{'open'};
842             my $ydir = ($lhs->[1]<$rhs->[1] ? 'n':'s'); my $yo = $lod->{$ydir}; $yo = 1 if ref $yo and $yo->{'open'};
843              
844             if( not $yo ) {
845             if( $j == $#path or ($path[$j+1][0] != $rhs->[0] ) ) {
846             splice @path, $j, 0, [ $lhs->[0], $rhs->[1] ]; # 0-width inserts at $j
847             redo DIAG_ORDEAL;
848              
849             } else {
850             $rhs->[0] = $lhs->[0];
851             }
852              
853             } elsif( not $xo ) {
854             if( $j == $#path or ($path[$j+1][1] != $rhs->[1] ) ) {
855             splice @path, $j, 0, [ $rhs->[0], $lhs->[1] ]; # 0-width inserts at $j
856             redo DIAG_ORDEAL;
857              
858             } else {
859             $rhs->[1] = $lhs->[1];
860             }
861             }
862             }
863             }
864             }
865             }
866              
867             return @path;
868             }
869             # }}}
870             # _door {{{
871             sub _door {
872 0     0     my $this = shift;
873 0 0         my $door = shift; return unless ref $door;
  0            
874              
875 0           for my $y ( 0 .. $this->{ym} ) {
876 0           for my $x ( 0 .. $this->{xm} ) {
877 0           my $tile = $this->{_the_map}[$y][$x];
878              
879 0           for my $d (qw(n e s w)) {
880 0 0         if( $door == $tile->{od}{$d} ) {
881 0           my $nb = $tile->{nb}{$d};
882              
883 0           return [$x,$y,$d];
884             }
885             }
886             }
887             }
888              
889 0           return;
890             }
891             # }}}
892            
893             # _line_segments_intersect {{{
894             sub _line_segments_intersect {
895 0     0     my $this = shift;
896             # this is http://perlmonks.org/?node_id=253983
897              
898 0           my ( $ax,$ay, $bx,$by, $cx,$cy, $dx,$dy ) = @_;
899             # printf STDERR "[pl] A(%9.6f,%9.6f) B(%9.6f,%9.6f) C(%9.6f,%9.6f) D(%9.6f,%9.6f)", $ax,$ay, $bx,$by, $cx,$cy, $dx,$dy;
900              
901             # P = p*A + (1-p)*B
902             # Q = q*C + (1-q)*D
903              
904             # for p=0, P=A, and for p=1, P=B
905             # for 0<=p<=1, P is on the line segment between A and B
906              
907             # find p,q such than P=Q
908             # (... lengthy derivation ...)
909              
910 0           my $d = ($ax-$bx)*($cy-$dy) - ($ay-$by)*($cx-$dx);
911             # printf STDERR " d=$d";
912              
913 0 0 0       if( $cx == $dx and $cy == $dy ) {
914             # 6/25/7 we're a point on the rhs ... apparently this happens when you remove the extrude shortcutting
915              
916 0 0 0       if( $ay == $by and $cy == $ay ) {
    0 0        
917 0 0 0       return ($cx, $cy) if $ax <= $cx and $cx <= $bx;
918              
919             } elsif( $ax == $bx and $cx == $ax ) {
920 0 0 0       return ($cx, $cy) if $ay <= $cy and $cy <= $by;
921             }
922              
923 0           die "probably a bug";
924             }
925              
926 0 0         if( $d == 0 ) {
927             # d=0 when len(C->D)==0 !!
928 0           for my $l ([$ax,$ay], [$bx, $by]) {
929 0           for my $r ([$cx,$cy], [$dx, $dy]) {
930 0 0 0       return (@$l) if $l->[0] == $r->[0] and $l->[1] == $r->[1];
931             }}
932              
933             # NOTE: another huge bug from 6/23/7 !! This vertical overlap was totally overlooked before.
934             # This is arguably not the most efficient way to check it, but it's literally better than *nothing*
935 0 0 0       if( abs($ax-$bx)<0.0001 and abs($bx-$cx)<0.0001 and abs($cx-$dx)<0.0001 ) {
    0 0        
      0        
      0        
936 0 0 0       return ($cx,$cy) if $ay <= $cy and $cy <= $by;
937 0 0 0       return ($dx,$dy) if $ay <= $dy and $dy <= $by;
938              
939             # 6/25/7 -- sorta the same deal as above, but horizontal
940             } elsif( abs($ay-$by)<0.0001 and abs($by-$cy)<0.0001 and abs($cy-$dy)<0.0001 ) {
941 0 0 0       return ($cx,$cy) if $ax <= $cx and $cx <= $bx;
942 0 0 0       return ($dx,$dy) if $ax <= $dx and $dx <= $bx;
943             }
944              
945             ## DEBUG ## warn "\t\tlsi p=||\n";
946 0           return; # probably parallel
947             }
948              
949 0           my $p = ( ($by-$dy)*($cx-$dx) - ($bx-$dx)*($cy-$dy) ) / $d;
950             # printf STDERR " p=$p";
951              
952             ## NOTE: this was an effin hard bug to find...
953             ## my @w = ( ( ($p <= 1) ? 1:0 ), ( ($p == 1) ? 1:0 ), ( ($p != 1) ? 1:0 ), ( ($p - 1) ),);
954             ## warn "\t\tlsi p=$p (@w)\n";
955             ## lsi p-1 = 2.22044604925031e-16 = 1? No, not actually, sometimes...
956              
957 0 0         $p = 0 if abs($p) < 0.00001; # fixed 6/23/7
958 0 0         $p = 1 if abs($p-1) < 0.00001;
959              
960             # printf STDERR " p=$p\n";
961              
962             ## DEBUG ## warn "\t\tlsi p=$p\n";
963              
964             # we probably don't need to find q because we already restricted the domain/range above
965 0 0 0       return unless $p >= 0 and $p <= 1;
966              
967 0           my $px = $p*$ax + (1-$p)*$bx;
968 0           my $py = $p*$ay + (1-$p)*$by;
969              
970 0           return ($px, $py);
971             }
972              
973             # NOTE: simply uncomment these to get verbose LSI results
974             ## DEBUG ## *debug_lsi = *_line_segments_intersect;
975             ## DEBUG ## sub replacer { my @ret = &debug_lsi(@_); warn "\t\tLSI(@ret)\n"; return @ret; }
976             ## DEBUG ## *_line_segments_intersect = *replacer;
977              
978             # }}}
979              
980             # location {{{
981             sub location {
982 0     0 1   my $this = shift;
983 0           my $that = shift;
984              
985 0 0         croak "that object/tag ($that) isn't on the map" unless exists $this->{l}{$that};
986              
987 0           my $l = $this->{l}{$that};
988 0 0         return (wantarray ? @$l : $l);
989             }
990             # }}}
991             # lline_of_sight {{{
992             sub lline_of_sight {
993 0     0 1   my $this = shift;
994              
995 0 0         croak "you should provide 4 arguments to line_of_sight()" unless @_ == 4;
996              
997 0           my @lhs = @_[0 .. 1];
998 0           my @rhs = @_[2 .. 3];
999              
1000 0 0         croak "the first two arguments to lline_of_sight() do not appear to form a sane map location" unless $this->_check_loc(\@lhs);
1001 0 0         croak "the last two arguments to lline_of_sight() do not appear to form a sane map location" unless $this->_check_loc(\@rhs);
1002              
1003 0           return $this->_line_of_sight(\@lhs, \@rhs);
1004             }
1005             # }}}
1006             # ldistance {{{
1007             sub ldistance {
1008 0     0 1   my $this = shift;
1009              
1010 0 0         croak "you should provide 4 arguments to ldistance()" unless @_ == 4;
1011              
1012 0           my @lhs = @_[0 .. 1];
1013 0           my @rhs = @_[2 .. 3];
1014              
1015 0 0         croak "the first two arguments to ldistance() do not appear to form a sane map location" unless $this->_check_loc(\@lhs);
1016 0 0         croak "the last two arguments to ldistance() do not appear to form a sane map location" unless $this->_check_loc(\@rhs);
1017              
1018 0 0         if( $_[4] ) {
1019 0           my @r = ($this->_ldistance(\@lhs, \@rhs), $this->_line_of_sight(\@lhs, \@rhs));
1020 0 0         return (wantarray ? @r : \@r);
1021             }
1022              
1023 0 0         return undef unless $this->_line_of_sight(\@lhs => \@rhs);
1024 0           return $this->_ldistance(\@lhs => \@rhs);
1025             }
1026             # }}}
1027             # distance {{{
1028             sub distance {
1029 0     0 1   my $this = shift;
1030 0 0         my $lhs = shift; croak "the lhs=$lhs isn't on the map" unless exists $this->{l}{$lhs};
  0            
1031 0 0         my $rhs = shift; croak "the rhs=$rhs isn't on the map" unless exists $this->{l}{$rhs};
  0            
1032 0           my $los = shift;
1033              
1034 0           $lhs = $this->{l}{$lhs};
1035 0           $rhs = $this->{l}{$rhs};
1036              
1037 0 0         if( $los ) {
1038 0           my @r = ($this->_ldistance($lhs, $rhs), $this->_line_of_sight($lhs, $rhs));
1039 0 0         return (wantarray ? @r : \@r);
1040             }
1041              
1042 0 0         return undef unless $this->_line_of_sight($lhs, $rhs);
1043 0           return $this->_ldistance($lhs, $rhs);
1044             }
1045             # }}}
1046             # line_of_sight {{{
1047             sub line_of_sight {
1048 0     0 1   my $this = shift;
1049              
1050 0 0         croak "you should provide 2 arguments to line_of_sight()" unless @_ == 2;
1051              
1052 0           my $lhs = shift; $lhs = "$lhs";
  0            
1053 0           my $rhs = shift; $rhs = "$rhs";
  0            
1054              
1055 0 0         croak "the first argument to line_of_sight() does not appear to be on the map" unless ($lhs = $this->{l}{$lhs});
1056 0 0         croak "the last argument to line_of_sight() does not appear to be on the map" unless ($rhs = $this->{l}{$rhs});
1057              
1058 0           return $this->_line_of_sight($lhs, $rhs);
1059             }
1060             # }}}
1061             # closure_line_of_sight {{{
1062             sub closure_line_of_sight {
1063 0     0 1   my $this = shift;
1064              
1065 0 0         croak "you should provide 2 arguments to closure_line_of_sight()" unless @_ == 2;
1066              
1067 0           my $lhs = shift; $lhs = "$lhs";
  0            
1068 0           my $rhs = shift;
1069              
1070 0 0         croak "the first argument to closure_line_of_sight() does not appear to be on the map" unless ($lhs = $this->{l}{$lhs});
1071 0 0         croak "the last argument to closure_line_of_sight() does not appear to be a door" unless ($rhs = $this->_door($rhs));
1072             # it definitely does have to be a door so we can get the direction! ... for arbitrary closures you must use
1073             # closure_lline_of_sight. :(
1074              
1075 0           return $this->_closure_line_of_sight($lhs, $rhs);
1076             }
1077             # }}}
1078             # closure_lline_of_sight {{{
1079             sub closure_lline_of_sight {
1080 0     0 1   my $this = shift;
1081              
1082 0 0         croak "you should provide 5 arguments to closure_lline_of_sight()" unless @_ == 5;
1083              
1084 0           my @lhs = @_[0 .. 1];
1085 0           my @rhs = @_[2 .. 3];
1086 0           my $dir = $_[4];
1087              
1088 0 0         croak "the first two arguments to closeure_lline_of_sight() do not appear to form a sane map location" unless $this->_check_loc(\@lhs);
1089 0 0         croak "the second two arguments to closeure_lline_of_sight() do not appear to form a sane map location" unless $this->_check_loc(\@rhs);
1090 0 0         croak "the fifth argument to closure_lline_of_sight() should be a map direction (ie, n s e w)" unless $dir =~ m/^[nsew]\z/;
1091              
1092 0           return $this->_closure_line_of_sight(\@lhs, [@rhs, $dir]);
1093             }
1094             # }}}
1095              
1096             # {{{ sub build_queue_from_hash
1097              
1098             ##################
1099             # XXX: experimental, undocumented, crazy thing, do not use, may change
1100             ######
1101              
1102             sub build_queue_from_hash {
1103 0     0 0   my $this = shift;
1104 0 0 0       my $that = @_==1 && ref($_[0])eq"HASH" ? $_[0] : { @_ };
1105              
1106 0           delete $this->{l};
1107 0           delete $this->{c};
1108              
1109 0           for my $k (keys %$that) {
1110 0           $this->{l} = $k;
1111 0           my $loc = $that->{$k}{l};
1112 0           my $itm = $that->{$k}{i};
1113              
1114 0           push @{$this->{c}[ $loc->[1] ][ $loc->[0] ]}, $itm;
  0            
1115             }
1116             }
1117              
1118             # }}}
1119              
1120             # add {{{
1121             sub add {
1122 0     0 1   my $this = shift;
1123 0 0         my $that = shift or croak "place what?"; my $tag = "$that";
  0            
1124 0           my @loc = @_;
1125              
1126 0 0         croak "that object/tag ($tag) appears to already be on the map" if exists $this->{l}{$tag};
1127 0 0         croak "that location (@loc) makes no sense" unless $this->_check_loc(\@loc);
1128              
1129 0           $this->{l}{$tag} = \@loc;
1130 0           push @{ $this->{c}[ $loc[1] ][ $loc[0] ] }, $that;
  0            
1131             }
1132             # }}}
1133             # remove {{{
1134             sub remove {
1135 0     0 1   my $this = shift;
1136 0           my $that = shift; my $tag = "$that";
  0            
1137              
1138 0 0         croak "that object/tag ($tag) isn't on the map" unless exists $this->{l}{$tag};
1139              
1140 0           my @loc = @{ delete $this->{l}{$tag} };
  0            
1141 0           my $itm = $this->{c}[ $loc[1] ][ $loc[0] ];
1142              
1143 0           @$itm = ( grep {$_ ne $tag} @$itm );
  0            
1144             }
1145             # }}}
1146             # replace {{{
1147             sub replace {
1148 0     0 1   my $this = shift;
1149 0           my $that = shift; my $tag = "$that";
  0            
1150 0           my @loc = @_;
1151              
1152 0 0         croak "that location (@loc) makes no sense" unless $this->_check_loc(\@loc);
1153              
1154 0 0         $this->remove($tag) if exists $this->{l}{$tag};
1155 0           $this->add($that => @loc);
1156             }
1157             # }}}
1158             # {{{ is_on_map
1159             sub is_on_map {
1160 0     0 0   my $this = shift;
1161 0           my $that = shift;
1162              
1163 0 0         return exists($this->{l}{$that}) ? 1:0;
1164             }
1165              
1166             # }}}
1167              
1168             # objs_at_location {{{
1169             sub objs_at_location {
1170 0     0 1   my $this = shift;
1171 0 0         my $loc = $this->_check_loc(\@_) or croak "that location (@_) makes no sense";
1172              
1173 0           return $this->_objs_at_location( $loc );
1174             }
1175             *objects_at_location = *objs_at_location;
1176             # }}}
1177             # objs_in_line_of_sight {{{
1178             sub objs_in_line_of_sight {
1179 0     0 1   my $this = shift;
1180 0 0         my $loc = $this->_check_loc(\@_) or croak "that location (@_) makes no sense";
1181 0           my @ret = ();
1182              
1183 0           for my $l ($this->_locations_in_line_of_sight($loc)) {
1184 0 0         push @ret, @{ $this->{c}[ $l->[1] ][ $l->[0] ] || [] };
  0            
1185             }
1186              
1187 0           return @ret;
1188             }
1189             *objects_in_line_of_sight = *objs_in_line_of_sight;
1190             # }}}
1191             # objs {{{
1192             sub objs {
1193 0     0 1   my $this = shift;
1194 0           my @ret = ();
1195              
1196 0           for my $row ( 0 .. $this->{ym} ) {
1197 0           for my $col ( 0 .. $this->{xm} ) {
1198              
1199 0 0         push @ret, @{ $this->{c}[ $row ][ $col ] || [] };
  0            
1200             }
1201             }
1202              
1203 0           return @ret;
1204             }
1205             *objects = *objs;
1206             # }}}
1207             # objs_with_locations {{{
1208             sub objs_with_locations {
1209 0     0 1   my $this = shift;
1210 0           my @ret = ();
1211              
1212 0           for my $row ( 0 .. $this->{ym} ) {
1213 0           for my $col ( 0 .. $this->{xm} ) {
1214 0           my $loc = [ $col, $row ];
1215              
1216 0 0         my @junk = @{ $this->{c}[ $loc->[1] ][ $loc->[0] ] || [] };
  0            
1217              
1218 0 0         push @ret, [ $loc => \@junk ] if @junk;
1219             }
1220             }
1221              
1222 0           return @ret;
1223             }
1224             *objects_with_locations = *objs_with_locations;
1225             # }}}
1226              
1227             # random_open_location {{{
1228             sub random_open_location {
1229 0     0 1   my $this = shift;
1230 0           my @l = $this->all_open_locations;
1231 0           my $i = int rand int @l;
1232              
1233 0 0         return unless @l;
1234 0 0         return (wantarray ? @{$l[$i]}:$l[$i]);
  0            
1235             }
1236             # }}}
1237             # all_open_locations {{{
1238             sub all_open_locations {
1239 0     0 1   my $this = shift;
1240 0           my ($X, $Y) = ($this->{xm}+1, $this->{ym}+1);
1241 0           my @ret = ();
1242              
1243 0           for my $x ( 0 .. $this->{xm} ) {
1244 0           for my $y ( 0 .. $this->{ym} ) {
1245 0 0         push @ret, [$x, $y] if defined $this->{_the_map}[ $y ][ $x ]{type}; # the wall type is
1246             }}
1247              
1248 0 0         return (wantarray ? @ret:\@ret);
1249             }
1250             # }}}
1251             # locations_in_line_of_sight {{{
1252             sub locations_in_line_of_sight {
1253 0     0 1   my $this = shift;
1254 0 0         my @init = @_; $this->_check_loc(\@init) or croak "that location (@_) doesn't make any sense";
  0            
1255              
1256 0           return $this->_locations_in_line_of_sight(\@init);
1257             }
1258             # }}}
1259             # locations_in_range_and_line_of_sight {{{
1260             sub locations_in_range_and_line_of_sight {
1261 0     0 1   my $this = shift;
1262 0 0         my @init = splice @_,0,2; $this->_check_loc(\@init) or croak "that location (@_) doesn't make any sense";
  0            
1263 0   0       my $range = shift || 0;
1264              
1265 0 0         croak "range should be greater than 0" unless $range > 0;
1266              
1267 0           return $this->_locations_in_range_and_line_of_sight(\@init, $range);
1268             }
1269             # }}}
1270             # locations_in_path {{{
1271             sub locations_in_path {
1272 0 0   0 1   my $this = shift; croak "you should provide 4 arguments to locations_in_path()" unless @_ == 4;
  0            
1273 0 0         my @lhs = @_[0 .. 1]; $this->_check_loc(\@lhs) or croak "the first two arguments to locations_in_path() (@_) don't make any sense";
  0            
1274 0 0         my @rhs = @_[2 .. 3]; $this->_check_loc(\@rhs) or croak "the second two arguments to locations_in_path() (@_) don't make any sense";
  0            
1275              
1276 0 0         croak "the target location doesn't appear to be visible from the source"
1277             unless $this->_line_of_sight(\@lhs => \@rhs);
1278              
1279 0           return $this->_locations_in_path(\@lhs => \@rhs);
1280             }
1281             # }}}
1282              
1283             # ranged_cover {{{
1284             sub ranged_cover {
1285 0     0 1   my $this = shift;
1286 0 0         my @l = @_[0 .. 1]; $this->_check_loc(\@l) or croak "the left location (@l) doesn't make any sense";
  0            
1287 0 0         my @r = @_[2 .. 3]; $this->_check_loc(\@r) or croak "the right location (@r) doesn't make any sense";
  0            
1288              
1289 0           return $this->_ranged_cover(\@l=>\@r);
1290             }
1291             # }}}
1292             # melee_cover {{{
1293             sub melee_cover {
1294 0     0 1   my $this = shift;
1295 0 0         my @l = @_[0 .. 1]; $this->_check_loc(\@l) or croak "the left location (@l) doesn't make any sense";
  0            
1296 0 0         my @r = @_[2 .. 3]; $this->_check_loc(\@r) or croak "the right location (@r) doesn't make any sense";
  0            
1297              
1298 0           return $this->_melee_cover(\@r=>\@l);
1299             }
1300             # }}}
1301             # ignorable_cover {{{
1302             sub ignorable_cover {
1303 0     0 0   my $this = shift;
1304 0 0         my @l = @_[0 .. 1]; $this->_check_loc(\@l) or croak "the left location (@l) doesn't make any sense";
  0            
1305 0 0         my @r = @_[2 .. 3]; $this->_check_loc(\@r) or croak "the right location (@r) doesn't make any sense";
  0            
1306              
1307 0           return $this->_ignorable_cover(\@r=>\@l);
1308             }
1309             # }}}
1310              
1311             # is_open {{{
1312             sub is_open {
1313 0     0 1   my $this = shift;
1314 0           my @loc = @_[0 .. 1];
1315              
1316 0           return $this->_check_loc(\@loc);
1317             }
1318             # }}}
1319             # is_door_open {{{
1320             sub is_door_open {
1321 0     0 1   my $this = shift;
1322 0           my @loc = @_[0 .. 1];
1323 0 0         my $dir = lc $_[2]; $dir = $1 if $dir =~ m/^([nsew])./i;
  0            
1324 0           my $door;
1325              
1326 0 0         croak "that location doesn't make sense" unless $this->_check_loc(\@loc);
1327 0 0         croak "there isn't a door there" unless ref ($door = $this->{_the_map}[ $loc[1] ][ $loc[0] ]{od}{$dir});
1328              
1329 0           return $door->{'open'};
1330             }
1331             # }}}
1332             # is_door {{{
1333             sub is_door {
1334 0     0 1   my $this = shift;
1335 0           my @loc = @_[0 .. 1];
1336 0 0         my $dir = lc $_[2]; $dir = $1 if $dir =~ m/^([nsew])./i;
  0            
1337              
1338 0 0         croak "that location doesn't make sense" unless $this->_check_loc(\@loc);
1339 0 0         return 1 if ref $this->{_the_map}[ $loc[1] ][ $loc[0] ]{od}{ $dir };
1340 0           return 0;
1341             }
1342             # }}}
1343             # open_door {{{
1344             sub open_door {
1345 0     0 1   my $this = shift;
1346 0           my @loc = @_[0 .. 1];
1347 0 0         my $dir = lc $_[2]; $dir = $1 if $dir =~ m/^([nsew])./i;
  0            
1348 0           my $door;
1349              
1350 0 0         croak "that location doesn't make sense" unless $this->_check_loc(\@loc);
1351 0 0         croak "there isn't a door there" unless ref ($door = $this->{_the_map}[ $loc[1] ][ $loc[0] ]{od}{$dir});
1352 0 0         croak "that door is already open" if $door->{'open'};
1353              
1354 0           $door->{'open'} = 1;
1355 0           $this->flush;
1356             }
1357             # }}}
1358             # close_door {{{
1359             sub close_door {
1360 0     0 1   my $this = shift;
1361 0           my @loc = @_[0 .. 1];
1362 0 0         my $dir = lc $_[2]; $dir = $1 if $dir =~ m/^([nsew])./i;
  0            
1363 0           my $door;
1364              
1365 0 0         croak "that location doesn't make sense" unless $this->_check_loc(\@loc);
1366 0 0         croak "there isn't a door there" unless ref ($door = $this->{_the_map}[ $loc[1] ][ $loc[0] ]{od}{$dir});
1367 0 0         croak "that door isn't open" unless $door->{'open'};
1368              
1369 0           $door->{'open'} = 0;
1370 0           $this->flush;
1371             }
1372             # }}}
1373             # map_range {{{
1374             sub map_range {
1375 0     0 1   my $this = shift;
1376              
1377 0 0         return ( 0 .. $this->{xm} ) if wantarray;
1378 0           return $this->{xm};
1379             }
1380             # }}}
1381             # map_domain {{{
1382             sub map_domain {
1383 0     0 1   my $this = shift;
1384              
1385 0 0         return ( 0 .. $this->{ym} ) if wantarray;
1386 0           return $this->{ym};
1387             }
1388             # }}}
1389              
1390             # {{{ FREEZE_THAW_HOOKS
1391             FREEZE_THAW_HOOKS: {
1392             my $going;
1393             sub STORABLE_freeze {
1394 0 0   0 0   return if $going;
1395 0           my $this = shift;
1396 0           $going = 1;
1397 0           my $str = freeze($this);
1398 0           $going = 0;
1399 0           return $str;
1400             }
1401              
1402             sub STORABLE_thaw {
1403 0     0 0   my $this = shift;
1404 0           %$this = %{ thaw($_[1]) };
  0            
1405 0           $this->retag;
1406             }
1407             }
1408              
1409             # }}}
1410              
1411             1;