File Coverage

blib/lib/OpenGL/QEng/GameState.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             ### $Id: GameState.pm 429 2008-08-19 20:00:43Z duncan $
2             ####------------------------------------------
3              
4             ## @file
5             # Define GameState Class
6             #
7             # Collection of items that make up a game
8              
9             ## @class GameState
10             # Container class holding all maps, chars & items
11             # Saving GameState saves all needed to continue the game
12             #
13             package OpenGL::QEng::GameState;
14              
15 2     2   1505 use strict;
  2         2  
  2         66  
16 2     2   10 use warnings;
  2         3  
  2         55  
17 2     2   9 use Carp;
  2         3  
  2         126  
18 2     2   1599 use File::ShareDir;
  2         12847  
  2         110  
19 2     2   1053 use OpenGL::QEng::Parser ':all';
  2         3  
  2         430  
20 2     2   940 use OpenGL::QEng::MapHash;
  0            
  0            
21             use OpenGL::QEng::Team;
22             use OpenGL::QEng::SimpleThing; # used for handle_give and load, because ST hides
23             # classes inside
24              
25             use base qw/OpenGL::QEng::Thing/;
26              
27             use constant PI => 4*atan2(1,1); # 3.14159;
28             use constant RADIANS => PI/180.0;
29             use constant DEGREES => 180.0/PI;
30              
31             #####
32             ##### Class Methods - called as Class->function($a,$b,$c)
33             #####
34              
35             ## @cmethod GameState new()
36             #
37             #Create a new GameState instance
38             #
39             sub new {
40             my ($class,@props) = @_;
41              
42             my $props = (scalar(@props) == 1) ? $props[0] : {@props};
43             my $self;
44             if (ref $class) {
45             $self = $class;
46             $class = ref $self;
47             for my $attr qw(maps cmap parts holds) {
48             undef $self->{$attr};
49             }
50             $self->{no_events} = 1;
51             }
52             else {
53             $self = OpenGL::QEng::Thing->new;
54             $self->{maps} = undef; # Hash of active maps
55             $self->{cmap} = undef; # key value for current map
56             $self->{team} = undef; # The Team
57             bless($self,$class);
58             }
59              
60             $self->passedArgs($props);
61             $self->assimilate($self->{team}) if defined $self->{team};
62             $self->assimilate($self->{maps}) if defined $self->{maps};
63             $self->create_accessors;
64             $self->register_events;
65              
66             $self;
67             }
68              
69             #--------------------------------------------------
70             sub boring_stuff {
71             my ($self) = @_;
72             my $boring_stuff = $self->SUPER::boring_stuff;
73             $boring_stuff->{team} = 1;
74             $boring_stuff->{maps} = 1;
75             $boring_stuff;
76             }
77              
78             #--------------------------------------------------
79             sub load {
80              
81             my ($self,$filename,$want_map,$x,$z,$yaw) = @_;
82              
83             my $class;
84             # if $self is a GameState, we are loading a Map or a saved game
85             # if it is a classname, we have no GameState yet, so we will make one
86             unless (ref($self)) {
87             $class = $self;
88             undef $self;
89             }
90             my %class_name;
91             for my $o (qw/Map Wall ArchWall Door WoodDoor BarDoor WallDoor Opening/,
92             qw/Box Beam Bank Sign Switch Chest Level Character Detector/,
93             qw/Hinged Part Torch Sconce Stair Team MapHash GameState/,
94             qw/MappingKit Treasure Key Helmet Sword Robe Shoes Letter/,
95             qw/Lamp Knife CHex/,
96             ) {
97             $class_name{lc $o} = $o;
98             }
99             if (!defined($filename) || $filename =~ /^maps/) {
100             my $mapdir = File::ShareDir::dist_dir('Games-Quest3D');
101             $filename = ($filename) ? "$mapdir/$filename"
102             : "$mapdir/maps/default_game.txt";
103             }
104             open(my $file,'<',$filename) or die "can't open $filename";
105             my $lines = records(join('',<$file>));
106             close $file;
107              
108             my $lexer = iterator_to_stream(
109             make_lexer($lines,
110             ['QSTRING', qr/(?:(?:\")(?:[^\\\"]*(?:\\.[^\\\"]*)*)(?:\")
111             |(?:\')(?:[^\\\']*(?:\\.[^\\\']*)*)(?:\'))/x],
112             ['TERMINATOR', qr/;\n*/, sub{['TERMINATOR',';']} ],
113             ['CONTEXT',
114             qr/\b(?:done|in_last|partof_last|partof_next|inventory)\b/i],
115             ['DEFINE', qr/\b(?:define|enddef)\b/i ],
116             ['COMPASS', qr|\b[ENSWensw]\b|,
117             sub { ['INTEGER',{n=>0,e=>90,s=>180,w=>270}->{lc $_[0]}] }],
118             ['WORD', qr|[A-Za-z_]\w*| ],
119             ['FLOAT', qr/[+-]{0,1}(?:\d+\.\d+)|(?:\.\d+)|(?:\d+\.)/ ],
120             ['INTEGER', qr/[+-]{0,1}\d+/ ],
121             ['FATARROW', qr/=>/ ],
122             ['COMMA', qr/,/ ],
123             ['LCURLY', qr/{/ ],
124             ['RCURLY', qr/}/ ],
125             ['LSQBR', qr/\[/ ],
126             ['RSQBR', qr/\]/ ],
127             ['WHITESPACE', qr/\s+/, sub{''} ],
128             ['UNKNOWN', qr/./, sub{die 'token?? [',join(',',@_),'] '}],
129             )
130             );
131             #..............................................................................
132             my $number = alternate(lookfor('FLOAT'), lookfor('INTEGER'));
133             my $position = concatenate($number,$number,$number);
134             my $hkey = alternate(lookfor('QSTRING'),
135             lookfor('WORD'),
136             $number);
137             my $hval;
138             my $Hval = sub { $hval->(@_) };
139             my $pair = concatenate($hkey,lookfor('FATARROW'),$Hval);
140             my $aref = concatenate(lookfor('LSQBR'), list_of($Hval),lookfor('RSQBR'));
141             my $href = concatenate(lookfor('LCURLY'),list_of($pair),lookfor('RCURLY'));
142             $hval = alternate($hkey,$aref,$href);
143              
144             my $where;
145             my $last;
146             my $storing = 1;
147             my @place;
148             my @mode;
149             my %name2obj;
150             my $statement =
151             alternate(
152             # class instance creation: make a Thing and put it in the map
153             T(alternate(concatenate(lookfor('WORD'),$position,list_of($pair),
154             lookfor('TERMINATOR')),
155             concatenate(lookfor('WORD'),$position,lookfor('COMMA'),
156             list_of($pair),lookfor('TERMINATOR')), ),
157             sub {
158             my $class = $class_name{lc $_[0]};
159             die "\nOops: $_[0] is not a known class of this game.\n",
160             "Check your map.\n\n" unless $class;
161             my @arg;
162             push @arg, x => $_[1][0];
163             push @arg, z => $_[1][1];
164             push @arg, yaw => $_[1][2];
165             my $par = ($_[2] eq ',') ? $_[3]: $_[2];
166             while (my $p = shift @$par) {
167             push @arg, digest_hval($p->[0][0]) => digest_hval($p->[0][2]);
168             if (ref($arg[-1]) eq 'HASH') {
169             if (exists $arg[-1]->{named}) {
170             $arg[-1] = $name2obj{$arg[-1]->{named}};
171             }
172             else {
173             for my $k (keys %{$arg[-1]}) {
174             undef($arg[-1]->{$k}) if $arg[-1]->{$k} eq 'undef';
175             }
176             }
177             }
178             }
179             require "OpenGL/QEng/$class.pm"
180             unless OpenGL::QEng::SimpleThing->has_subclass($class);
181             if ($class eq 'Map' && exists {@arg}->{file}) {
182             my $cmap = $self->cmap if defined $self;
183             $last = $self->load({@arg}->{file},
184             'map please',$arg[1],$arg[3],$arg[5]);
185             if ($cmap && $where && $where->isa('OpenGL::QEng::Map')) {
186             $self->cmap($cmap);
187             }
188             }
189             else {
190             if ($class eq 'GameState' && ref $self) {
191             $last = $self->new(@arg);
192             }
193             elsif ($class eq 'Team' && $self->{team}) {
194             $last = $self->team->new(@arg);
195             $self->excise($self->team);
196             }
197             else {
198             $last = "OpenGL::QEng::$class"->new(@arg);
199             }
200             }
201             if (exists {@arg}->{name}) {
202             my $name = {@arg}->{name};
203             $name2obj{$name} = $last;
204             }
205             if ($class eq 'GameState') {
206             $self = $where = $last;
207             unless (defined $self->maps) {
208             $self->maps(OpenGL::QEng::MapHash->new);
209             $self->assimilate($self->{maps});
210             }
211             unless (defined $self->team) {
212             $self->team(OpenGL::QEng::Team->new);
213             $self->assimilate($self->{team});
214             }
215             $storing = 0;
216             }
217             elsif (!defined $where) {
218             die 'no map or gamestate' unless ref $last eq 'OpenGL::QEng::Map';
219             unless (defined $self) {
220             # make a gamestate, team, and maphash
221             $self = OpenGL::QEng::GameState->new(team => OpenGL::QEng::Team->new,
222             maps => OpenGL::QEng::MapHash->new);
223             $self->team->start(@{$last->start},$last);
224             }
225             $where = $last;
226             if (defined $x) {
227             $last->{x} = $x;
228             $last->{z} = $z||0;
229             $last->{yaw} = $yaw||0;
230             }
231             $last->{textMap} = $filename;
232             $self->maps->assimilate($last);
233             $self->add_map($last,$filename);
234             $storing = 1;
235             }
236             elsif ($storing) {
237             $where->put_thing($last,1);
238             }
239             else {
240             $where->assimilate($last) unless $where eq 'noplace';
241             }
242             }
243             ),
244              
245             # control where the next things get put
246             T(concatenate(lookfor('CONTEXT'),lookfor('TERMINATOR')),
247             sub {
248             my ($place) = @_;
249             if ($place eq 'in_last' || $place eq 'partof_last') {
250             push @place, $where;
251             push @mode, $storing;
252             $where = $last;
253             $storing = ($place eq 'in_last') ? 1 : 0;
254             }
255             elsif ($place eq 'partof_next') {
256             push @place, $where;
257             push @mode, $storing;
258             $where = 'noplace';
259             $storing = 0;
260             }
261             elsif ($place eq 'done') {
262             die 'stack underflow' unless @place;
263             $where = pop @place;
264             $storing = pop @mode;
265             }
266             elsif ($place eq 'inventory') {
267             push @place, $where;
268             push @mode, $storing;
269             $where = $self->team;
270             }
271             }),
272              
273             # macro definition
274             T(concatenate(lookfor('DEFINE'),
275             lookfor('WORD'), # then body...
276             lookfor('DEFINE'),lookfor('TERMINATOR')),
277             sub {
278             my (@args) = @_;
279             die "macro def = (",join(',',@_),")"
280             }),
281              
282             # empty statement
283             T(lookfor('TERMINATOR'),
284             sub { }),
285             );
286              
287             my $mapper = star($statement);
288             my ($result, $remains) = $mapper->($lexer);
289              
290             if (defined $remains) {
291             require Data::Dumper;
292             print "------------- remains ---------------\n";
293             print Data::Dumper->Dump($remains),"\n";
294             }
295             return $self->{maps}{$filename} if $want_map;
296             $self->send_event('new_map',$self->currmap); #let the overview know
297             $self;
298             }
299              
300             #####
301             ##### Object Methods
302             #####
303              
304             #--------------------------------------------------
305             sub register_events {
306             my ($self) = @_;
307              
308             return if $self->no_events;
309             for my $event (['map' => \&switch_map ],
310             ['dropped' => \&handle_drop ],
311             ['grabbed' => \&handle_grab ],
312             ['give_team' => \&handle_give ],
313             ['step_team' => \&performStep ],
314             ['try_unlock' => \&try_unlock ],
315             ['touched_map'=> \&handle_touch ],
316             ['remove_me' => \&handle_remove],
317             ['need_redraw'=> \&check_collision],
318             ) {
319             $self->{event}->callback($self,$event->[0],$event->[1]);
320             }
321             # XXX just for testing -- remove me
322             $self->{event}->notify($self,'special',
323             sub {$self->send_event('who_is','doggy door')});
324             $self->{event}->notify($self,'i_am',
325             sub {
326             my ($self,$stash,$obj,@args) = @_;
327             $self->{event}->callback($self,'special',
328             sub {
329             $obj->handle_touch($self->team);
330             #$obj->printMe;
331             });
332             });
333             # XXX end of just for testing
334             }
335              
336             #--------------------------------------------------
337             ## @method %map currmap([$map,$key])
338             # return the map associated with the given key
339             #
340             # If called without a key, the current map is returned
341             sub currmap {
342             my ($self,$key) = @_;
343              
344             if ($key) {
345             if (defined $self->{maps}{$key}) {
346             $self->cmap($key);
347             }
348             else { # !!! temp hack
349             die 'new map case';
350             }
351             }
352             die "currmap($self,",$key||'',") cmap=$self->{cmap} called from ",
353             join(':',caller),' ' unless $self->cmap;
354             $self->{maps}{$self->cmap};
355             }
356              
357             #---------------------------------
358             ## @method add_map($map,$key)
359             #Add a map with the given key
360             sub add_map {
361             my ($self,$map,$key) = @_;
362              
363             $self->cmap($key);
364             $self->{maps}{$key} = $map;
365             }
366              
367             #---------------------------------
368             ## @method save($filename)
369             #Save the state of the game on the given file
370             # $filename - file to save on
371             sub save {
372             my ($self,$filename) = @_;
373              
374             local *STDOUT;
375             open STDOUT,'>',$filename or die "Unable to redirect STDOUT";
376             $self->printMe;
377             }
378              
379             #---------------------------------
380             sub switch_map {
381             my ($self,undef,undef,undef,$filename,@transition) = @_;
382              
383             if ($filename =~ /^maps/) {
384             my $mapdir = File::ShareDir::dist_dir('Games-Quest3D');
385             $filename = "$mapdir/$filename";
386             }
387             my $new_map = $self->{maps}{$filename};
388             if ($new_map) {
389             $self->{cmap} = $filename;
390             # set the team at start
391             $self->team->start(@{ $new_map->start},$new_map);
392             $self->send_event('new_map',$self->currmap); #let the overview know
393             }
394             else {
395             $self->load_map($filename,0,@transition);
396             }
397             }
398              
399             #---------------------------------
400             sub load_map {
401             my ($self,$filename,$saved_position,@transition) = @_;
402              
403             my $map1;
404             if ($filename =~ /^maps/) {
405             my $mapdir = File::ShareDir::dist_dir('Games-Quest3D');
406             $filename = "$mapdir/$filename";
407             }
408             if (-f $filename) {
409             if (@transition) {
410             $self->team->adjust_picture(@transition);
411             }
412             $map1 = $self->load($filename,'map please');
413             $self->add_map($map1,$filename);
414             } else {
415             print "Can't locate file $filename\n";
416             exit(-1);
417             }
418              
419             # set the team at start
420             $self->team->start(@{$map1->start},$map1) unless $saved_position;
421             $self->send_event('new_map',$map1); #let the overview know
422              
423             $self;
424             }
425              
426             #--------------------------
427             sub send_event { $_[0]->{event}->yell(@_) }
428              
429              
430             #------------------------------------------
431             sub digest_hval {
432             my ($hv) = @_;
433              
434             return unless defined $hv;
435             if (ref $hv) { # $hv is an ARRAY ref
436             if ($hv->[0] eq '[') {
437             my $ar = $hv->[1];
438             $hv = [];
439             while (my $li = shift @$ar) {
440             $li = digest_hval($li->[0]);
441             $li =~ s/^[\'\"]//;
442             $li =~ s/[\'\"]$//;
443             push @$hv, $li;
444             }
445             }
446             elsif ($hv->[0] eq '{') {
447             my $ar = $hv->[1];
448             $hv = [];
449             while (my $li = shift @$ar) {
450             $li = $li->[0];
451             my $k = digest_hval($li->[0]);
452             my $v = digest_hval($li->[2]);
453             push @$hv, $k => $v;
454             }
455             $hv = {@$hv};
456             }
457             else {
458             die 'digest_hval(',join(',',@$hv),") $hv called from ",
459             join(':',caller),"\n";
460             }
461             } else {
462             my @m;
463             if (@m = $hv =~ /^\'(.*)\'$/) {
464             $hv = $m[0];
465             }
466             if (@m = $hv =~ /^\"(.*)\"$/) {
467             $hv = $m[0];
468             }
469             $hv =~ s/\\n/\n/g;
470             $hv =~ s/\\'/'/g;
471             $hv =~ s/\\"/"/g;
472             }
473              
474             $hv;
475             }
476              
477             #--------------------------
478             ## @method assimilate($thing)
479             # make $thing a part of $self
480             #
481             sub assimilate {
482             my ($self,$thing) = @_;
483              
484             return unless defined($thing);
485             if ($thing->isa('OpenGL::QEng::MapHash')) {
486             $self->{maps} = $thing;
487             }
488             elsif ($thing->isa('OpenGL::QEng::Team')) {
489             $self->{team} = $thing;
490             }
491             $self->SUPER::assimilate($thing);
492             }
493              
494             #--------------------------------------------------
495             sub handle_give {
496             my ($self,$stash,$obj,$ev,$class,@arg) = @_;
497              
498             require 'OpenGL/QEng/'.$class.'.pm'
499             unless OpenGL::QEng::SimpleThing->has_subclass($class);
500             $self->team->put_thing("OpenGL::QEng::$class"->new(@arg),1);
501             }
502              
503             #--------------------------------------------------
504             ## @method handle_touch($callback_args,$source_obj, $ev_type,$name, @args)
505             # default touch handler method for things on the map
506             # Pass the touch event to the touched object
507             sub handle_touch {
508             my ($self,$callback_args,$source_obj,$ev_type,$GLid) = @_;
509              
510             my $thing = OpenGL::QEng::Thing->find_thing_by_GLid($GLid);
511             $thing->handle_touch($self->team) if ref $thing;
512             }
513              
514             #-----------------------------------------------------------
515             sub try_unlock {
516             my ($self,$stash,$thing,$ev) = @_;
517              
518             my $testkey = (defined $self->team->using)
519             ? $self->team->holds->[$self->team->using]
520             : undef;
521             $thing->unlock($testkey);
522             }
523              
524             #-----------------------------------------------------------
525             ## @method handle_grab()
526             # Grab an item for the team
527             sub handle_grab {
528             my ($self,$stash,$item,$ev,$where_i_was) = @_;
529              
530             my $items_carried = scalar @{$self->team->contains};
531             if ($items_carried < $self->team->max_contains) {
532             $self->team->put_thing($item);
533             } else {
534             $self->send_event('msg',
535             "Uh oh, aleady holding $items_carried things\n",
536             "Maybe we should drop something...\n", );
537             confess "$self wasn't anywhere" unless ref $where_i_was;
538             # store back in 'holds' array of last container
539             $where_i_was->put_thing($item,1);
540             }
541             }
542              
543             #-----------------------------------------------------------
544             ## @method handle_drop()
545             # Drop an item either at the team's feet or onto a surface
546             sub handle_drop {
547             my ($self,$stash,$item,$ev) = @_;
548              
549             return unless defined($item);
550              
551             my $map = $self->currmap or die 'no current map';
552             my $team = $self->team;
553             my $tx = $team->x;
554             my $ty = $team->y;
555             my $tz = $team->z;
556             my $tyaw = -$team->yaw+90; # adjust for coordinate systems
557             my ($thing,$surface);
558             my $min_dist = 2.5;
559             # find point $min_dist ft in front of the team
560             my $p2x_ = $tx+$min_dist*sin($tyaw*RADIANS);
561             my $p2z_ = $tz+$min_dist*cos($tyaw*RADIANS);
562              
563             $map->get_map_view;
564             $map->find_objects;
565             foreach my $obj (@{$map->{objects}}) {
566             my ($ox,$oy,$oz,$or) = @$obj;
567             if ($or->can_hold($item)) {
568             my ($p2x,$p2z) = ($p2x_,$p2z_);
569             my $touch = 0;
570             my @sides = (defined $or->{tlines}) ? @{$or->{tlines}} : ();
571             for my $side (@sides) {
572             next unless defined $side;
573             my ($p2rx,$p2rz) = intersect($side->[0],$side->[1],
574             $side->[2],$side->[3],
575             $tx,$tz,$p2x,$p2z);
576             unless ($p2rx == -1 && $p2rz == -1) {
577             $p2x = $p2rx; # Locate the nearest encounter
578             $p2z = $p2rz;
579             $touch = 1;
580             $thing = $side->[6];
581             }
582             }
583             if ($touch) {
584             my $dist_2 = (($tx-$ox)*($tx-$ox) + ($tz-$oz)*($tz-$oz));
585             if ($dist_2 < ($min_dist*$min_dist)) {
586             $min_dist = sqrt($dist_2);
587             $surface = $thing;
588             }
589             }
590             }
591             }
592              
593             if (defined $surface) {
594             $surface->put_thing($item,1);
595             } else {
596             # else drop at team feet (1/4" above floor)
597             $item->x($tx);
598             $item->z($tz);
599             $item->y(($ty-5.5)+0.02);
600             $map->put_thing($item,1);
601             }
602             }
603              
604             #------------------------------------------------
605             sub check_collision {
606             my ($self,$stash,$sender,$ev,$not_moving,@args) = @_;
607              
608             return if $not_moving or
609             $sender eq 'main' or $sender==$self or $sender==$self->{team};
610             my $min_dist = 3;
611             my $tx = $self->team->x;
612             my $tz = $self->team->z;
613             my $tyaw = -$self->team->yaw+90; # adjust for coordinate systems
614              
615             my $container = $sender;
616             while ($container->isa('OpenGL::QEng::Part')) { $container = $container->is_at; }
617             # find point $min_dist ft in front of the team
618             my $px = $tx+$min_dist*sin($tyaw*RADIANS);
619             my $pz = $tz+$min_dist*cos($tyaw*RADIANS);
620             my $touch = 0;
621             $container->find_objects;
622             if ($container->{objects}) {
623             foreach my $obj (@{$container->{objects}}) {
624             my ($ox,$oy,$oz,$or) = @$obj;
625             next unless (($ox-$tx)*($ox-$tx)+($oz-$tz)*($oz-$oz)) < 10;
626             next unless $or->{tlines};
627             for my $side (@{$or->{tlines}}) {
628             next unless defined $side;
629             my ($prx,$prz) = intersect($side->[0],$side->[1],
630             $side->[2],$side->[3],
631             $tx,$tz,$px,$pz);
632             unless ($prx == -1 && $prz == -1) {
633             $px = $prx; # Locate the nearest encounter
634             $pz = $prz;
635             $touch = 1;
636             }
637             }
638             }
639             }
640             my $dist = 1000;
641             $dist = sqrt(($px-$tx)*($px-$tx)+($pz-$tz)*($pz-$tz)) if ($touch);
642              
643             if ($dist < 1) { #XXX how far?
644             $self->send_event('collision',$container,$sender);
645             }
646             }
647              
648             #------------------------------------------------------------------------------
649             {;
650             my $lastx = -999;
651             my $lastz = -999;
652             my $lastdir = -999;
653             my $lastdist = 0;;
654              
655             sub performStep {
656             my ($self,$stash,$team,$ev,$steps,$speed,$direction) = @_;
657              
658             # Handle possibiity of no map during initialization
659             return unless defined $self->{cmap};
660              
661             if ($speed < 0) {
662             $direction = ($direction+180) % 360;
663             $speed = -$speed;
664             }
665             my @min_dist = (10.0,10.0,10.0,10.0); # look out 10 feet
666              
667             ## find teams field of view
668             my $tx = $team->x;
669             my $tz = $team->z;
670             my $tyaw = -$team->yaw+90; # adjust for coordinate systems
671             my $pyaw = 65;
672             my $step = $speed*$steps;
673             my $moveYaw = $team->yaw + $direction;
674              
675             if ($ENV{DESLUG} && $tx==$lastx && $tz==$lastz && $moveYaw==$lastdir
676             && $lastdist>$step+.5) {
677             $team->x($team->x+$step*cos($moveYaw*RADIANS));
678             $team->z($team->z+$step*sin($moveYaw*RADIANS));
679             $min_dist[3] = $lastdist - $step;
680             }
681             else {
682             # left v center v right v travel
683             my @p_ = (['x','y'],['x','y'],['x','y'],['x','y']);
684              
685             # find point $min_dist ft out on the left peripherial vision ray
686             $p_[0][0] = $tx+$min_dist[0]*sin(($tyaw-$pyaw)*RADIANS);
687             $p_[0][1] = $tz+$min_dist[0]*cos(($tyaw-$pyaw)*RADIANS);
688              
689             # find point $min_dist ft in front of the team
690             $p_[1][0] = $tx+$min_dist[1]*sin($tyaw*RADIANS);
691             $p_[1][1] = $tz+$min_dist[1]*cos($tyaw*RADIANS);
692              
693             # find point $min_dist ft out on the right peripherial vision ray
694             $p_[2][0] = $tx+$min_dist[2]*sin(($tyaw+$pyaw)*RADIANS);
695             $p_[2][1] = $tz+$min_dist[2]*cos(($tyaw+$pyaw)*RADIANS);
696              
697             # find point $min_dist ft out in the direction of travel
698             $p_[3][0] = $tx+$min_dist[3]*sin(($tyaw - $direction)*RADIANS);
699             $p_[3][1] = $tz+$min_dist[3]*cos(($tyaw - $direction)*RADIANS);
700              
701             my @seen_maybe = ([],[],[]);
702             my ($obstacle,$tractable,$thing);
703             my $map = $self->currmap;
704             $map->get_map_view;
705             $map->find_objects;
706             my ($oc,$ic) = (0,0);
707             foreach my $o (@{$map->{objects}}) {
708             $oc++;
709             my ($ox,$oy,$oz,$or) = @$o;
710             next unless (($ox-$tx)*($ox-$tx)+($oz-$tz)*($oz-$oz)) < 100;
711             next if $or == $map;
712             my @sides = (defined $or->{tlines}) ? @{$or->{tlines}} : ();
713             for my $i (0..3) {
714             my ($px,$pz) = ($p_[$i][0],$p_[$i][1]);
715             my $touch = 0;
716             for my $side (@sides) {
717             next unless defined $side;
718             $ic++;
719             my ($prx,$prz) = intersect($side->[0],$side->[1],
720             $side->[2],$side->[3],
721             $tx,$tz,$px,$pz);
722             unless ($prx == -1 && $prz == -1) {
723             $px = $prx; # Locate the nearest encounter
724             $pz = $prz;
725             $touch = 1;
726             $tractable = $side->[5];
727             $thing = $side->[6];
728             }
729             }
730             if ($touch) {
731             my $dist = sqrt(($px-$tx)*($px-$tx)+($pz-$tz)*($pz-$tz));
732             if ($dist < $min_dist[$i]) {
733             if ($i < 3) { # checking for 'seen'
734             push @{$seen_maybe[$i]}, [$dist,$thing];
735             if ($tractable eq 'solid') { # this will stop us, so
736             ($p_[$i][0],$p_[$i][1]) = ($px,$pz); # only look out this far
737             # from now on
738             $min_dist[$i] = $dist;
739             }
740             }
741             else { # checking for obstacle to travel
742             if ($tractable ne 'passable') { # this will stop us, so
743             ($p_[$i][0],$p_[$i][1]) = ($px,$pz); # only look out this far
744             # from now on
745             $min_dist[$i] = $dist;
746             $obstacle = $or;
747             }
748             }
749             }
750             }
751             }
752             }
753             # sort out what is 'seen'
754             my $thingsSeen = 0; # things requiring nodding
755             my $nodDist = 6.0; # look out 6 feet
756             for my $i (0..3) {
757             for my $candidate (@{$seen_maybe[$i]}) {
758             if ($candidate->[0] <= $min_dist[$i]) {
759             $candidate->[1]->{seen} = 'true';
760             # Check if looking down needed
761             if ($candidate->[1]->can('make_me_nod')
762             && $candidate->[1]->make_me_nod ) {
763             if ($candidate->[0] <= $nodDist) {
764             # make the team look down (nod)
765             $thingsSeen++;
766             my $elev = $team->y-1;
767             my $atan2val = -atan2($elev,$candidate->[0])*DEGREES;
768             $team->{target}{pitch} = $atan2val;
769             }
770             }
771             }
772             }
773             }
774             ## stop looking down if nothing in sight on the floor
775             $team->{target}{pitch} = 0 unless $thingsSeen;
776              
777             #my $step = $speed*$steps;
778             return if ($step==0 and $direction==0);
779              
780             #Move the team, if possible
781             my $dist = $min_dist[3];
782             if ($dist >= abs($step)+0.5 || $ENV{'WIZARD'}) {
783             my $moveYaw = $team->yaw + $direction;
784             $team->x($team->x+$step*cos($moveYaw*RADIANS));
785             $team->z($team->z+$step*sin($moveYaw*RADIANS));
786             print STDERR "$obstacle is in our way\n"
787             if ($ENV{'WIZARD'} && $dist < abs($step)+0.5);
788             } else {
789             print "Bang!!\n";
790             $self->send_event('msg',"Bang!!\n");
791             $self->send_event('bell');
792             }
793             }
794             $lastx = $team->x; $lastz = $team->z; $lastdir = $moveYaw;
795             $lastdist = $min_dist[3];
796              
797             $team->is_at($self->currmap);
798             $self->send_event('team_at',$team->x,$team->z,$self->currmap);
799             $self->send_event('need_redraw');
800             }
801             }
802              
803             #--------------------------------------------------
804             ### From Paul Bourke
805             # http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d/
806             #
807             sub intersect {
808             my ($x1,$y1,$x2,$y2,$x3,$y3,$x4,$y4) = @_;
809              
810             my $denom = (($y4-$y3)*($x2-$x1)-($x4-$x3)*($y2-$y1));
811             if ($denom == 0) {
812             return (-1,-1);
813             }
814             my $ua = (($x4-$x3)*($y1-$y3)-($y4-$y3)*($x1-$x3))/$denom;
815             my $ub = (($x2-$x1)*($y1-$y3)-($y2-$y1)*($x1-$x3))/$denom;
816              
817             if (($ua<0) || ($ua>1) || ($ub<0) || ($ub>1)) {
818             return (-1,-1);
819             }
820              
821             return ($x1+$ua*($x2-$x1),$y1+$ua*($y2-$y1));
822             }
823              
824             #==================================================================
825             ###
826             ### Test Driver
827             ###
828             if (!defined(caller())) {
829             package main;
830              
831             print "gameState\n";
832             #my $g = OpenGL::QEng::GameState->new;
833              
834             open(my $m,'>','/tmp/gs_testmap.txt');
835             print $m "map 0 0 0 xsize=>24, zsize=>24;\n";
836             print $m "in_last;\n";
837             print $m " wall 16 0 270;\n";
838             print $m "done;\n";
839             close $m;
840              
841             #$g->load('/tmp/gs_testmap.txt','I want a map');
842             my $g = GameState->load('/tmp/gs_testmap.txt');
843             print "$g\n";
844             print "bye\n";
845             }
846              
847             1;
848              
849             __END__