line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Term::Animation; |
2
|
|
|
|
|
|
|
|
3
|
1
|
|
|
1
|
|
25186
|
use 5.006; |
|
1
|
|
|
|
|
4
|
|
|
1
|
|
|
|
|
39
|
|
4
|
1
|
|
|
1
|
|
5
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
34
|
|
5
|
1
|
|
|
1
|
|
4
|
use warnings; |
|
1
|
|
|
|
|
7
|
|
|
1
|
|
|
|
|
35
|
|
6
|
1
|
|
|
1
|
|
5
|
use Carp; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
117
|
|
7
|
1
|
|
|
1
|
|
480
|
use Curses; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
use Term::Animation::Entity; |
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
use Data::Dumper; |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
=head1 NAME |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
Term::Animation - ASCII sprite animation framework |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
=head1 SYNOPSIS |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
use Term::Animation; |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
# Constructors |
21
|
|
|
|
|
|
|
$anim = Term::Animation->new(); |
22
|
|
|
|
|
|
|
$anim = Term::Animation->new($curses_window); |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
=head1 ABSTRACT |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
A framework to produce sprite animations using ASCII art. |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
=head1 DESCRIPTION |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
This module provides a framework to produce sprite animations using |
31
|
|
|
|
|
|
|
ASCII art. Each ASCII 'sprite' is given one or more frames, and placed |
32
|
|
|
|
|
|
|
into the animation as an 'animation object'. An animation object can |
33
|
|
|
|
|
|
|
have a callback routine that controls the position and frame of the |
34
|
|
|
|
|
|
|
object. |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
If the constructor is passed no arguments, it assumes that it is |
37
|
|
|
|
|
|
|
running full screen, and behaves accordingly. Alternatively, it can |
38
|
|
|
|
|
|
|
accept a curses window (created with the Curses I call) as an |
39
|
|
|
|
|
|
|
argument, and will draw into that window. |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
=head1 EXAMPLES |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
This example moves a small object across the screen from left to right. |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
use Term::Animation; |
46
|
|
|
|
|
|
|
use Curses; |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
$anim = Term::Animation->new(); |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
# set the delay for getch |
51
|
|
|
|
|
|
|
halfdelay( 2 ); |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
# create a simple shape we can move around |
54
|
|
|
|
|
|
|
$shape = "<=O=>"; |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
# turn our shape into an animation object |
57
|
|
|
|
|
|
|
$anim->new_entity( |
58
|
|
|
|
|
|
|
shape => $shape, # object shape |
59
|
|
|
|
|
|
|
position => [3, 7, 10], # row / column / depth |
60
|
|
|
|
|
|
|
callback_args => [1, 0, 0, 0], # the default callback |
61
|
|
|
|
|
|
|
# routine takes a list |
62
|
|
|
|
|
|
|
# of x,y,z,frame deltas |
63
|
|
|
|
|
|
|
wrap => 1 # turn screen wrap on |
64
|
|
|
|
|
|
|
); |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
# animation loop |
67
|
|
|
|
|
|
|
while(1) { |
68
|
|
|
|
|
|
|
# run and display a single animation frame |
69
|
|
|
|
|
|
|
$anim->animate(); |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
# use getch to control the frame rate, and get input at the |
72
|
|
|
|
|
|
|
# same time. (not a good idea if you are expecting much input) |
73
|
|
|
|
|
|
|
my $input = getch(); |
74
|
|
|
|
|
|
|
if($input eq 'q') { last; } |
75
|
|
|
|
|
|
|
} |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
# cleanly end the animation, to avoid hosing up the user's terminal |
78
|
|
|
|
|
|
|
$anim->end(); |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
This illustrates how to draw your animation into an existing Curses window. |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
use Term::Animation; |
83
|
|
|
|
|
|
|
use Curses; |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
# Term::Animation will not call initscr for you if |
86
|
|
|
|
|
|
|
# you pass it a window |
87
|
|
|
|
|
|
|
initscr(); |
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
$win = newwin(5,10,8,7); |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
$anim = Term::Animation->new($win); |
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
Everything else would be identical to the previous example. |
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
=head1 METHODS |
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
=over 4 |
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
=cut |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
our $VERSION = '2.6'; |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
our ($color_names, $color_ids) = _color_list(); |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
=item I |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
$anim = Term::Animation->new(); |
108
|
|
|
|
|
|
|
$anim = Term::Animation->new($curses_window); |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
The constructor. Optionally takes an existing curses window |
111
|
|
|
|
|
|
|
to draw in. |
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
=cut |
114
|
|
|
|
|
|
|
sub new { |
115
|
|
|
|
|
|
|
my $proto = shift; |
116
|
|
|
|
|
|
|
my $class = ref($proto) || $proto; |
117
|
|
|
|
|
|
|
my $self = {}; |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
$self->{ENTITIES} = {}; |
120
|
|
|
|
|
|
|
$self->{ENTITYCOUNT} = 0; |
121
|
|
|
|
|
|
|
$self->{PHYSICALENTITIES} = {}; |
122
|
|
|
|
|
|
|
$self->{PHYSICALCOUNT} = 0; |
123
|
|
|
|
|
|
|
$self->{COLOR_ENABLED} = 0; |
124
|
|
|
|
|
|
|
$self->{LAST_FRAME_TIME} = 0; |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
# framerate related settings |
127
|
|
|
|
|
|
|
$self->{TRACK_FRAMERATE} = 1; |
128
|
|
|
|
|
|
|
$self->{FRAMERATE} = 0; |
129
|
|
|
|
|
|
|
$self->{FRAMES_THIS_SECOND} = 0; |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
$self->{WIN} = shift; |
132
|
|
|
|
|
|
|
if(defined($self->{WIN})) { |
133
|
|
|
|
|
|
|
unless(ref($self->{WIN}) eq 'Curses::Window') { |
134
|
|
|
|
|
|
|
carp("Expecting Curses::Window object, recieved " . ref($self->{WIN})); |
135
|
|
|
|
|
|
|
return undef; |
136
|
|
|
|
|
|
|
} |
137
|
|
|
|
|
|
|
$self->{FULLSCREEN} = 0; |
138
|
|
|
|
|
|
|
} else { |
139
|
|
|
|
|
|
|
# this is the method in the docs... |
140
|
|
|
|
|
|
|
$self->{WIN} = new Curses; |
141
|
|
|
|
|
|
|
# ...but apparently it's broken with some versions of Curses or ncurses. |
142
|
|
|
|
|
|
|
# this seems to work everywhere, but the Curses.pm docs |
143
|
|
|
|
|
|
|
# say to call the constructor when using objects. |
144
|
|
|
|
|
|
|
unless(defined($self->{WIN})) { |
145
|
|
|
|
|
|
|
$self->{WIN} = Curses::initscr(); |
146
|
|
|
|
|
|
|
} |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
noecho(); |
149
|
|
|
|
|
|
|
curs_set(0); |
150
|
|
|
|
|
|
|
$self->{FULLSCREEN} = 1; |
151
|
|
|
|
|
|
|
} |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
($self->{WIDTH}, $self->{HEIGHT}, $self->{ASSUMED_SIZE}) = _get_term_size($self->{WIN}); |
154
|
|
|
|
|
|
|
bless ($self, $class); |
155
|
|
|
|
|
|
|
return $self; |
156
|
|
|
|
|
|
|
} |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
sub DESTROY { |
159
|
|
|
|
|
|
|
my ($self) = @_; |
160
|
|
|
|
|
|
|
if($self->{FULLSCREEN}) { |
161
|
|
|
|
|
|
|
endwin(); |
162
|
|
|
|
|
|
|
} |
163
|
|
|
|
|
|
|
} |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
=item I |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
$anim->new_entity( |
168
|
|
|
|
|
|
|
shape => $shape, |
169
|
|
|
|
|
|
|
position => [ 1, 2, 3 ], |
170
|
|
|
|
|
|
|
callback_args => [ 1, 0, 0 ] |
171
|
|
|
|
|
|
|
); |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
Creates a new Term::Animation::Entity object and adds it to the |
174
|
|
|
|
|
|
|
animation. This is identical to: |
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
my $entity = Term::Animation::Entity->new(...); |
177
|
|
|
|
|
|
|
$anim->add_entity($entity); |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
See L and L |
180
|
|
|
|
|
|
|
in L for details on calling this method. |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
=cut |
183
|
|
|
|
|
|
|
sub new_entity { |
184
|
|
|
|
|
|
|
my ($self, @ent_args) = @_; |
185
|
|
|
|
|
|
|
my $entity = Term::Animation::Entity->new(@ent_args); |
186
|
|
|
|
|
|
|
$self->add_entity($entity); |
187
|
|
|
|
|
|
|
return $entity; |
188
|
|
|
|
|
|
|
} |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
##################### COLOR UTILITIES ####################### |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
# create lists mapping full color names (eg. 'blue') and |
193
|
|
|
|
|
|
|
# single character color ids (eg. 'b') |
194
|
|
|
|
|
|
|
sub _color_list { |
195
|
|
|
|
|
|
|
my %color_n; |
196
|
|
|
|
|
|
|
my %color_i = ( |
197
|
|
|
|
|
|
|
black => 'k', |
198
|
|
|
|
|
|
|
white => 'w', |
199
|
|
|
|
|
|
|
red => 'r', |
200
|
|
|
|
|
|
|
green => 'g', |
201
|
|
|
|
|
|
|
blue => 'b', |
202
|
|
|
|
|
|
|
cyan => 'c', |
203
|
|
|
|
|
|
|
magenta => 'm', |
204
|
|
|
|
|
|
|
yellow => 'y', |
205
|
|
|
|
|
|
|
); |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
for (keys %color_i) { |
208
|
|
|
|
|
|
|
$color_i{uc($_)} = uc($color_i{$_}); |
209
|
|
|
|
|
|
|
} |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
for (keys %color_i) { |
212
|
|
|
|
|
|
|
$color_n{$color_i{$_}} = $_; |
213
|
|
|
|
|
|
|
$color_n{$_} = $_; |
214
|
|
|
|
|
|
|
$color_n{uc($_)} = uc($_); |
215
|
|
|
|
|
|
|
} |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
for(qw{ k w r g b c m y }) { |
218
|
|
|
|
|
|
|
$color_i{$_} = $_; |
219
|
|
|
|
|
|
|
$color_i{uc($_)} = uc($_); |
220
|
|
|
|
|
|
|
} |
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
return (\%color_n, \%color_i); |
223
|
|
|
|
|
|
|
} |
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
# build a list of every color combination for our current |
226
|
|
|
|
|
|
|
# background color |
227
|
|
|
|
|
|
|
sub _set_colors { |
228
|
|
|
|
|
|
|
my ($self) = @_; |
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
my $cid = 1; |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
my $bg = eval "Curses::COLOR_$self->{BG}"; |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
for my $f ('w', 'r', 'g', 'b', 'c', 'm', 'y', 'k') { |
235
|
|
|
|
|
|
|
my $c = uc(color_name($f)); |
236
|
|
|
|
|
|
|
init_pair($cid, eval "Curses::COLOR_$c", $bg); |
237
|
|
|
|
|
|
|
$self->{COLORS}{$f} = COLOR_PAIR($cid); |
238
|
|
|
|
|
|
|
$cid++; |
239
|
|
|
|
|
|
|
} |
240
|
|
|
|
|
|
|
} |
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
=item I |
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
$name = $anim->color_name( $color ); |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
Returns the full name of a color, given either a full |
247
|
|
|
|
|
|
|
name or a single character abbreviation. |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
=cut |
250
|
|
|
|
|
|
|
sub color_name { |
251
|
|
|
|
|
|
|
my ($color) = @_; |
252
|
|
|
|
|
|
|
if(defined($color_names->{$color})) { |
253
|
|
|
|
|
|
|
return $color_names->{$color}; |
254
|
|
|
|
|
|
|
} else { |
255
|
|
|
|
|
|
|
carp("Attempt to allocate unknown color: $color"); |
256
|
|
|
|
|
|
|
return undef; |
257
|
|
|
|
|
|
|
} |
258
|
|
|
|
|
|
|
} |
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
=item I |
261
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
$id = $anim->color_id( $color ); |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
Returns the single character abbreviation for a color, |
265
|
|
|
|
|
|
|
given either a full name or abbreviation. |
266
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
=cut |
268
|
|
|
|
|
|
|
sub color_id { |
269
|
|
|
|
|
|
|
my ($color) = @_; |
270
|
|
|
|
|
|
|
if(defined($color_ids->{$color})) { |
271
|
|
|
|
|
|
|
return $color_ids->{$color}; |
272
|
|
|
|
|
|
|
} else { |
273
|
|
|
|
|
|
|
carp("Attempt to allocate unknown color: $color"); |
274
|
|
|
|
|
|
|
return undef; |
275
|
|
|
|
|
|
|
} |
276
|
|
|
|
|
|
|
} |
277
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
=item I |
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
my $is_valid = $anim->is_valid_color($color_name); |
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
Returns true if the supplied string is a valid color name ('blue') |
283
|
|
|
|
|
|
|
or a valid color id ('b'). |
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
=cut |
286
|
|
|
|
|
|
|
sub is_valid_color { |
287
|
|
|
|
|
|
|
my ($color) = @_; |
288
|
|
|
|
|
|
|
return(defined($color_ids->{$color})); |
289
|
|
|
|
|
|
|
} |
290
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
=item I |
292
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
my $state = $anim->color(); |
294
|
|
|
|
|
|
|
$anim->color($new_state); |
295
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
Enable or disable ANSI color. This MUST be called immediately after creating |
297
|
|
|
|
|
|
|
the animation object if you want color, because the Curses start_color call must |
298
|
|
|
|
|
|
|
be made immediately. You can then turn color on and off whenever you want. |
299
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
=cut |
301
|
|
|
|
|
|
|
sub color { |
302
|
|
|
|
|
|
|
my $self = shift; |
303
|
|
|
|
|
|
|
if(@_) { |
304
|
|
|
|
|
|
|
my $enable = shift; |
305
|
|
|
|
|
|
|
if($enable != $self->{COLOR_ENABLED}) { |
306
|
|
|
|
|
|
|
if($enable) { |
307
|
|
|
|
|
|
|
start_color(); |
308
|
|
|
|
|
|
|
unless(defined($self->{BG})) { $self->{BG} = 'BLACK'; } |
309
|
|
|
|
|
|
|
$self->_set_colors(); |
310
|
|
|
|
|
|
|
$self->{WIN}->bkgdset($self->{COLORS}{'w'}); |
311
|
|
|
|
|
|
|
} |
312
|
|
|
|
|
|
|
$self->{COLOR_ENABLED} = $enable; |
313
|
|
|
|
|
|
|
} |
314
|
|
|
|
|
|
|
} |
315
|
|
|
|
|
|
|
return $self->{COLOR_ENABLED}; |
316
|
|
|
|
|
|
|
} |
317
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
=item I |
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
$anim->background( $color ); |
321
|
|
|
|
|
|
|
|
322
|
|
|
|
|
|
|
Change the background color. The default background color is black. You |
323
|
|
|
|
|
|
|
can only have one background color for the entire Curses window that |
324
|
|
|
|
|
|
|
the animation is running in. |
325
|
|
|
|
|
|
|
|
326
|
|
|
|
|
|
|
=cut |
327
|
|
|
|
|
|
|
sub background { |
328
|
|
|
|
|
|
|
my $self = shift; |
329
|
|
|
|
|
|
|
if(@_) { |
330
|
|
|
|
|
|
|
my $color = shift; |
331
|
|
|
|
|
|
|
my $bg_color = color_name($color); |
332
|
|
|
|
|
|
|
if(defined($bg_color)) { |
333
|
|
|
|
|
|
|
$self->{BG} = uc($bg_color); |
334
|
|
|
|
|
|
|
$self->_set_colors(); |
335
|
|
|
|
|
|
|
$self->{WIN}->bkgdset($self->{COLORS}{'w'}); |
336
|
|
|
|
|
|
|
} |
337
|
|
|
|
|
|
|
} |
338
|
|
|
|
|
|
|
return $self->{BG}; |
339
|
|
|
|
|
|
|
} |
340
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
########## END COLOR UTILITIES ########### |
342
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
########################## PHYSICS UTILITIES ########################## |
344
|
|
|
|
|
|
|
|
345
|
|
|
|
|
|
|
|
346
|
|
|
|
|
|
|
# go through all of the physical entities looking for |
347
|
|
|
|
|
|
|
# collisions. |
348
|
|
|
|
|
|
|
sub _find_collisions { |
349
|
|
|
|
|
|
|
my ($self) = @_; |
350
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
my @col_set = (); |
352
|
|
|
|
|
|
|
my @coord = (); |
353
|
|
|
|
|
|
|
my @size = (); |
354
|
|
|
|
|
|
|
my @name = (); |
355
|
|
|
|
|
|
|
|
356
|
|
|
|
|
|
|
for my $ent (values %{$self->{ENTITIES}}) { |
357
|
|
|
|
|
|
|
next unless($ent->physical()); |
358
|
|
|
|
|
|
|
push(@coord, [ $ent->position() ]); |
359
|
|
|
|
|
|
|
push(@size, [ $ent->size() ]); |
360
|
|
|
|
|
|
|
push(@name, $ent->name()); |
361
|
|
|
|
|
|
|
|
362
|
|
|
|
|
|
|
for my $i (0..($#name-1)) { |
363
|
|
|
|
|
|
|
# X |
364
|
|
|
|
|
|
|
if( ($coord[$i][0] <= $coord[-1][0] and $coord[-1][0] < $coord[$i][0] + $size[$i][0]) or |
365
|
|
|
|
|
|
|
($coord[-1][0] <= $coord[$i][0] and $coord[$i][0] < $coord[-1][0] + $size[-1][0]) ) { |
366
|
|
|
|
|
|
|
# Y |
367
|
|
|
|
|
|
|
if( ($coord[$i][1] <= $coord[-1][1] and $coord[-1][1] < $coord[$i][1] + $size[$i][1]) or |
368
|
|
|
|
|
|
|
($coord[-1][1] <= $coord[$i][1] and $coord[$i][1] < $coord[-1][1] + $size[-1][1]) ) { |
369
|
|
|
|
|
|
|
# Z |
370
|
|
|
|
|
|
|
if( ($coord[$i][2] <= $coord[-1][2] and $coord[-1][2] < $coord[$i][2] + $size[$i][2]) or |
371
|
|
|
|
|
|
|
($coord[-1][2] <= $coord[$i][2] and $coord[$i][2] < $coord[-1][2] + $size[-1][2]) ) { |
372
|
|
|
|
|
|
|
push( @{$ent->{COLLISIONS}}, $self->{ENTITIES}{$name[$i]} ); |
373
|
|
|
|
|
|
|
push( @{$self->{ENTITIES}{$name[$i]}{COLLISIONS}}, $ent ); |
374
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
} |
376
|
|
|
|
|
|
|
} |
377
|
|
|
|
|
|
|
} |
378
|
|
|
|
|
|
|
} |
379
|
|
|
|
|
|
|
} |
380
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
return; |
382
|
|
|
|
|
|
|
} |
383
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
# update the list of physical entities when the physical state |
385
|
|
|
|
|
|
|
# of an entity changes |
386
|
|
|
|
|
|
|
sub _update_physical { |
387
|
|
|
|
|
|
|
my ($self, $entity) = @_; |
388
|
|
|
|
|
|
|
if($entity->{PHYSICAL} && !defined($self->{PHYSICALENTITIES}{$entity->{NAME}})) { |
389
|
|
|
|
|
|
|
$self->{PHYSICALCOUNT}++; |
390
|
|
|
|
|
|
|
$self->{PHYSICALENTITIES}{$entity->{NAME}} = $entity; |
391
|
|
|
|
|
|
|
} elsif(defined($self->{PHYSICALENTITIES}{$entity->{NAME}})) { |
392
|
|
|
|
|
|
|
$self->{PHYSICALCOUNT}--; |
393
|
|
|
|
|
|
|
delete $self->{PHYSICALENTITIES}{$entity->{NAME}}; |
394
|
|
|
|
|
|
|
} |
395
|
|
|
|
|
|
|
} |
396
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
########## END PHYSICS UTILITIES ########### |
398
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
=item I |
400
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
$anim->animate(); |
402
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
Perform a single animation cycle. Runs all of the callbacks, |
404
|
|
|
|
|
|
|
does collision detection, and updates the display. |
405
|
|
|
|
|
|
|
|
406
|
|
|
|
|
|
|
=cut |
407
|
|
|
|
|
|
|
sub animate { |
408
|
|
|
|
|
|
|
my ($self) = @_; |
409
|
|
|
|
|
|
|
$self->_do_callbacks(); |
410
|
|
|
|
|
|
|
if($self->{PHYSICALCOUNT} > 0) { |
411
|
|
|
|
|
|
|
$self->_find_collisions(); |
412
|
|
|
|
|
|
|
$self->_collision_handlers(); |
413
|
|
|
|
|
|
|
} |
414
|
|
|
|
|
|
|
$self->_remove_deleted_entities(); |
415
|
|
|
|
|
|
|
$self->_move_followers(); |
416
|
|
|
|
|
|
|
$self->_build_screen(); |
417
|
|
|
|
|
|
|
$self->_display_screen(); |
418
|
|
|
|
|
|
|
$self->_track_frame_rate() if $self->{TRACK_FRAMERATE}; |
419
|
|
|
|
|
|
|
} |
420
|
|
|
|
|
|
|
|
421
|
|
|
|
|
|
|
sub _track_frame_rate { |
422
|
|
|
|
|
|
|
my ($self) = @_; |
423
|
|
|
|
|
|
|
my $time = time(); |
424
|
|
|
|
|
|
|
if($time > $self->{LAST_FRAME_TIME}) { |
425
|
|
|
|
|
|
|
$self->{LAST_FRAME_TIME} = $time; |
426
|
|
|
|
|
|
|
$self->{FRAMERATE} = ($self->{FRAMERATE} + ($self->{FRAMES_THIS_SECOND} * 2) ) / 3; |
427
|
|
|
|
|
|
|
$self->{FRAMES_THIS_SECOND} = 1; |
428
|
|
|
|
|
|
|
} else { |
429
|
|
|
|
|
|
|
$self->{FRAMES_THIS_SECOND}++; |
430
|
|
|
|
|
|
|
} |
431
|
|
|
|
|
|
|
} |
432
|
|
|
|
|
|
|
|
433
|
|
|
|
|
|
|
=item I |
434
|
|
|
|
|
|
|
|
435
|
|
|
|
|
|
|
$anim->track_framerate(1); |
436
|
|
|
|
|
|
|
$tracking_framerate = $anim->track_framerate(); |
437
|
|
|
|
|
|
|
|
438
|
|
|
|
|
|
|
Get or set the flag that indicates whether the module |
439
|
|
|
|
|
|
|
should keep track of the animation framerate. This is |
440
|
|
|
|
|
|
|
enabled by default. |
441
|
|
|
|
|
|
|
|
442
|
|
|
|
|
|
|
=cut |
443
|
|
|
|
|
|
|
sub track_framerate { |
444
|
|
|
|
|
|
|
my ($self) = @_; |
445
|
|
|
|
|
|
|
if(@_) { |
446
|
|
|
|
|
|
|
$self->{TRACK_FRAMERATE} = shift; |
447
|
|
|
|
|
|
|
} |
448
|
|
|
|
|
|
|
return $self->{TRACK_FRAMERATE}; |
449
|
|
|
|
|
|
|
} |
450
|
|
|
|
|
|
|
|
451
|
|
|
|
|
|
|
=item I |
452
|
|
|
|
|
|
|
|
453
|
|
|
|
|
|
|
$frames_per_second = $anim->framerate(); |
454
|
|
|
|
|
|
|
|
455
|
|
|
|
|
|
|
Returns the approximate number of frames being displayed |
456
|
|
|
|
|
|
|
per second, as indicated by calls to the I method. |
457
|
|
|
|
|
|
|
|
458
|
|
|
|
|
|
|
=cut |
459
|
|
|
|
|
|
|
sub framerate { |
460
|
|
|
|
|
|
|
my ($self) = @_; |
461
|
|
|
|
|
|
|
return $self->{FRAMERATE}; |
462
|
|
|
|
|
|
|
} |
463
|
|
|
|
|
|
|
|
464
|
|
|
|
|
|
|
=item I |
465
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
my ($width, $height, $assumed_size) = $anim->screen_size(); |
467
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
Returns the width and height of the screen. The third value |
469
|
|
|
|
|
|
|
returned is a boolean indicating whether or not the default |
470
|
|
|
|
|
|
|
screen size was used, because the size could not be determined. |
471
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
=cut |
473
|
|
|
|
|
|
|
sub screen_size { |
474
|
|
|
|
|
|
|
my $self = shift; |
475
|
|
|
|
|
|
|
return($self->{WIDTH}, $self->{HEIGHT}, $self->{ASSUMED_SIZE}); |
476
|
|
|
|
|
|
|
} |
477
|
|
|
|
|
|
|
|
478
|
|
|
|
|
|
|
=item I |
479
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
$anim->update_term_size(); |
481
|
|
|
|
|
|
|
|
482
|
|
|
|
|
|
|
Call this if you suspect the terminal size has changed (eg. if you |
483
|
|
|
|
|
|
|
get a SIGWINCH signal). Call I after this if |
484
|
|
|
|
|
|
|
you want to recreate your animation from scratch. |
485
|
|
|
|
|
|
|
|
486
|
|
|
|
|
|
|
=cut |
487
|
|
|
|
|
|
|
sub update_term_size { |
488
|
|
|
|
|
|
|
my $self = shift; |
489
|
|
|
|
|
|
|
# dunno how portable this is. i should probably be using |
490
|
|
|
|
|
|
|
# resizeterm. |
491
|
|
|
|
|
|
|
endwin(); |
492
|
|
|
|
|
|
|
refresh(); |
493
|
|
|
|
|
|
|
($self->{WIDTH}, $self->{HEIGHT}, $self->{ASSUMED_SIZE}) = _get_term_size($self->{WIN}); |
494
|
|
|
|
|
|
|
} |
495
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
# try to figure out the terminal size, and set |
497
|
|
|
|
|
|
|
# a reasonable size if we can't. the 'assumed_size' |
498
|
|
|
|
|
|
|
# variable will let programs know if we had to |
499
|
|
|
|
|
|
|
# guess or not. |
500
|
|
|
|
|
|
|
sub _get_term_size { |
501
|
|
|
|
|
|
|
my $win = shift; |
502
|
|
|
|
|
|
|
my ($width, $height, $assumed_size); |
503
|
|
|
|
|
|
|
# find the width and height of the terminal |
504
|
|
|
|
|
|
|
$width = $win->getmaxx(); |
505
|
|
|
|
|
|
|
$height = $win->getmaxy(); |
506
|
|
|
|
|
|
|
if($width and $height) { |
507
|
|
|
|
|
|
|
$assumed_size = 0; # so we know if we can limit the max size or not |
508
|
|
|
|
|
|
|
} else { |
509
|
|
|
|
|
|
|
$assumed_size = 1; |
510
|
|
|
|
|
|
|
$width = 80; |
511
|
|
|
|
|
|
|
$height = 24; |
512
|
|
|
|
|
|
|
} |
513
|
|
|
|
|
|
|
return($width, $height, $assumed_size); |
514
|
|
|
|
|
|
|
} |
515
|
|
|
|
|
|
|
|
516
|
|
|
|
|
|
|
# write to the curses window |
517
|
|
|
|
|
|
|
sub _build_screen { |
518
|
|
|
|
|
|
|
my($self) = @_; |
519
|
|
|
|
|
|
|
|
520
|
|
|
|
|
|
|
# clear the window before we start redrawing |
521
|
|
|
|
|
|
|
$self->{WIN}->addstr( 0, 0, ' 'x$self->size() ); |
522
|
|
|
|
|
|
|
|
523
|
|
|
|
|
|
|
return unless($self->{ENTITYCOUNT}); |
524
|
|
|
|
|
|
|
foreach my $entity (sort {$b->{'Z'} <=> $a->{'Z'}} values %{$self->{ENTITIES}}) { |
525
|
|
|
|
|
|
|
_draw_entity($self, $entity); |
526
|
|
|
|
|
|
|
} |
527
|
|
|
|
|
|
|
} |
528
|
|
|
|
|
|
|
|
529
|
|
|
|
|
|
|
# draw an entity into the curses window in memory |
530
|
|
|
|
|
|
|
sub _draw_entity { |
531
|
|
|
|
|
|
|
my ($self, $entity) = @_; |
532
|
|
|
|
|
|
|
|
533
|
|
|
|
|
|
|
# a few temporary variables to make the code below easier to read |
534
|
|
|
|
|
|
|
my $shape = $entity->{SHAPE}[$entity->{CURR_FRAME}]; |
535
|
|
|
|
|
|
|
my $colors = $self->{COLORS}; |
536
|
|
|
|
|
|
|
my $fg = $entity->{COLOR}[$entity->{CURR_FRAME}]; |
537
|
|
|
|
|
|
|
my $attrs = $entity->{ATTR}[$entity->{CURR_FRAME}]; |
538
|
|
|
|
|
|
|
my ($x, $y) = ($entity->{'X'}, $entity->{'Y'}); |
539
|
|
|
|
|
|
|
my ($w, $h) = ($self->{WIDTH}, $self->{HEIGHT}); |
540
|
|
|
|
|
|
|
my $wrap = $entity->{WRAP}; |
541
|
|
|
|
|
|
|
my $trans = $entity->{TRANSPARENT}; |
542
|
|
|
|
|
|
|
my $win = $self->{WIN}; |
543
|
|
|
|
|
|
|
my $color_enabled = $self->{COLOR_ENABLED}; |
544
|
|
|
|
|
|
|
my $attr; |
545
|
|
|
|
|
|
|
|
546
|
|
|
|
|
|
|
for my $i (0..$#{$shape}) { |
547
|
|
|
|
|
|
|
my $y_pos = $y+$i; |
548
|
|
|
|
|
|
|
|
549
|
|
|
|
|
|
|
for my $j (0..$#{$shape->[$i]}) { |
550
|
|
|
|
|
|
|
unless($shape->[$i][$j] eq $trans) { # transparent char |
551
|
|
|
|
|
|
|
my $x_pos = $x+$j; |
552
|
|
|
|
|
|
|
|
553
|
|
|
|
|
|
|
if($wrap) { |
554
|
|
|
|
|
|
|
while($x_pos >= $w) { $x_pos -= $w; } |
555
|
|
|
|
|
|
|
while($y_pos >= $h) { $y_pos -= $h; } |
556
|
|
|
|
|
|
|
} elsif($x_pos >= $w or $y_pos >= $h) { |
557
|
|
|
|
|
|
|
next; |
558
|
|
|
|
|
|
|
} |
559
|
|
|
|
|
|
|
|
560
|
|
|
|
|
|
|
unless($x_pos < 0 or $y_pos < 0) { |
561
|
|
|
|
|
|
|
if($color_enabled) { |
562
|
|
|
|
|
|
|
if(defined($attrs->[$i][$j])) { |
563
|
|
|
|
|
|
|
$attr = $colors->{$fg->[$i][$j]} | $attrs->[$i][$j]; |
564
|
|
|
|
|
|
|
} else { |
565
|
|
|
|
|
|
|
$attr = $colors->{$fg->[$i][$j]}; |
566
|
|
|
|
|
|
|
} |
567
|
|
|
|
|
|
|
|
568
|
|
|
|
|
|
|
$win->attron( $attr ); |
569
|
|
|
|
|
|
|
$win->addstr( int($y_pos), int($x_pos), $shape->[$i][$j]); |
570
|
|
|
|
|
|
|
$win->attroff( $attr ); |
571
|
|
|
|
|
|
|
} else { |
572
|
|
|
|
|
|
|
$win->addstr( int($y_pos), int($x_pos), $shape->[$i][$j]); |
573
|
|
|
|
|
|
|
} |
574
|
|
|
|
|
|
|
} |
575
|
|
|
|
|
|
|
} |
576
|
|
|
|
|
|
|
} |
577
|
|
|
|
|
|
|
} |
578
|
|
|
|
|
|
|
} |
579
|
|
|
|
|
|
|
|
580
|
|
|
|
|
|
|
=item I |
581
|
|
|
|
|
|
|
|
582
|
|
|
|
|
|
|
$anim->add_entity( $entity1, $entity2, $entity3 ); |
583
|
|
|
|
|
|
|
|
584
|
|
|
|
|
|
|
Add one or more animation entities to the animation. |
585
|
|
|
|
|
|
|
|
586
|
|
|
|
|
|
|
=cut |
587
|
|
|
|
|
|
|
sub add_entity { |
588
|
|
|
|
|
|
|
my ($self, @entities) = @_; |
589
|
|
|
|
|
|
|
foreach my $entity (@entities) { |
590
|
|
|
|
|
|
|
$self->{ENTITYCOUNT}++; |
591
|
|
|
|
|
|
|
if($entity->{PHYSICAL}) { |
592
|
|
|
|
|
|
|
$self->{PHYSICALCOUNT}++; |
593
|
|
|
|
|
|
|
$self->{PHYSICALENTITIES}{$entity->{NAME}} = $entity; |
594
|
|
|
|
|
|
|
} |
595
|
|
|
|
|
|
|
$self->{ENTITIES}{$entity->{NAME}} = $entity; |
596
|
|
|
|
|
|
|
$entity->{ANIMATION} = $self; |
597
|
|
|
|
|
|
|
} |
598
|
|
|
|
|
|
|
} |
599
|
|
|
|
|
|
|
|
600
|
|
|
|
|
|
|
=item I |
601
|
|
|
|
|
|
|
|
602
|
|
|
|
|
|
|
$anim->del_entity( $entity_name ); |
603
|
|
|
|
|
|
|
$anim->del_entity( $entity_ref ); |
604
|
|
|
|
|
|
|
|
605
|
|
|
|
|
|
|
Removes an entity from the animation. Accepts either an entity |
606
|
|
|
|
|
|
|
name or a reference to the entity itself. |
607
|
|
|
|
|
|
|
|
608
|
|
|
|
|
|
|
=cut |
609
|
|
|
|
|
|
|
sub del_entity { |
610
|
|
|
|
|
|
|
my ($self, $entity) = @_; |
611
|
|
|
|
|
|
|
if(ref($entity)) { |
612
|
|
|
|
|
|
|
$entity = $entity->name(); |
613
|
|
|
|
|
|
|
} |
614
|
|
|
|
|
|
|
if(defined($self->{ENTITIES}{$entity})) { |
615
|
|
|
|
|
|
|
push(@{$self->{DELETEQUEUE}}, $entity); |
616
|
|
|
|
|
|
|
} else { |
617
|
|
|
|
|
|
|
carp("Attempted to destroy nonexistant entity '$entity'"); |
618
|
|
|
|
|
|
|
} |
619
|
|
|
|
|
|
|
} |
620
|
|
|
|
|
|
|
|
621
|
|
|
|
|
|
|
# go through the list of entities that have been queued for |
622
|
|
|
|
|
|
|
# deletion using del_entity and remove them |
623
|
|
|
|
|
|
|
sub _remove_deleted_entities { |
624
|
|
|
|
|
|
|
my ($self) = @_; |
625
|
|
|
|
|
|
|
while(my $entity_name = shift @{$self->{DELETEQUEUE}}) { |
626
|
|
|
|
|
|
|
my $entity = $self->{ENTITIES}{$entity_name}; |
627
|
|
|
|
|
|
|
if(defined($entity->{DEATH_CB})) { |
628
|
|
|
|
|
|
|
$entity->{DEATH_CB}->($entity, $self); |
629
|
|
|
|
|
|
|
} |
630
|
|
|
|
|
|
|
if($entity->{PHYSICAL}) { |
631
|
|
|
|
|
|
|
$self->{PHYSICALCOUNT}--; |
632
|
|
|
|
|
|
|
delete $self->{PHYSICALENTITIES}{$entity_name}; |
633
|
|
|
|
|
|
|
} |
634
|
|
|
|
|
|
|
delete $self->{ENTITIES}{$entity_name}; |
635
|
|
|
|
|
|
|
$self->{ENTITYCOUNT}--; |
636
|
|
|
|
|
|
|
} |
637
|
|
|
|
|
|
|
} |
638
|
|
|
|
|
|
|
|
639
|
|
|
|
|
|
|
=item I |
640
|
|
|
|
|
|
|
|
641
|
|
|
|
|
|
|
$anim->remove_all_entities(); |
642
|
|
|
|
|
|
|
|
643
|
|
|
|
|
|
|
Removes every animation object. This is useful if you need to start the |
644
|
|
|
|
|
|
|
animation over (eg. after a screen resize) |
645
|
|
|
|
|
|
|
|
646
|
|
|
|
|
|
|
=cut |
647
|
|
|
|
|
|
|
sub remove_all_entities { |
648
|
|
|
|
|
|
|
my ($self) = @_; |
649
|
|
|
|
|
|
|
$self->{ENTITYCOUNT} = 0; |
650
|
|
|
|
|
|
|
$self->{PHYSICALCOUNT} = 0; |
651
|
|
|
|
|
|
|
$self->{PHYSICALENTITIES} = {}; |
652
|
|
|
|
|
|
|
$self->{ENTITIES} = {}; |
653
|
|
|
|
|
|
|
} |
654
|
|
|
|
|
|
|
|
655
|
|
|
|
|
|
|
=item I |
656
|
|
|
|
|
|
|
|
657
|
|
|
|
|
|
|
$number_of_entities = $anim->entity_count(); |
658
|
|
|
|
|
|
|
|
659
|
|
|
|
|
|
|
Returns the number of entities in the animation. |
660
|
|
|
|
|
|
|
|
661
|
|
|
|
|
|
|
=cut |
662
|
|
|
|
|
|
|
sub entity_count { |
663
|
|
|
|
|
|
|
my ($self) = @_; |
664
|
|
|
|
|
|
|
my $count = 0; |
665
|
|
|
|
|
|
|
foreach (keys %{$self->{ENTITIES}}) { |
666
|
|
|
|
|
|
|
$count++; |
667
|
|
|
|
|
|
|
} |
668
|
|
|
|
|
|
|
return $count; |
669
|
|
|
|
|
|
|
} |
670
|
|
|
|
|
|
|
|
671
|
|
|
|
|
|
|
=item I |
672
|
|
|
|
|
|
|
|
673
|
|
|
|
|
|
|
$entity_list = $anim->get_entities(); |
674
|
|
|
|
|
|
|
|
675
|
|
|
|
|
|
|
Returns a reference to a list of all entities in the animation. |
676
|
|
|
|
|
|
|
|
677
|
|
|
|
|
|
|
=cut |
678
|
|
|
|
|
|
|
sub get_entities { |
679
|
|
|
|
|
|
|
my ($self) = @_; |
680
|
|
|
|
|
|
|
my @entities = keys %{$self->{ENTITIES}}; |
681
|
|
|
|
|
|
|
return \@entities; |
682
|
|
|
|
|
|
|
} |
683
|
|
|
|
|
|
|
|
684
|
|
|
|
|
|
|
=item I |
685
|
|
|
|
|
|
|
|
686
|
|
|
|
|
|
|
$entity_list = $anim->get_entities_of_type( $type ); |
687
|
|
|
|
|
|
|
|
688
|
|
|
|
|
|
|
Returns a reference to a list of all entities in the animation |
689
|
|
|
|
|
|
|
that have the given type. |
690
|
|
|
|
|
|
|
|
691
|
|
|
|
|
|
|
=cut |
692
|
|
|
|
|
|
|
sub get_entities_of_type { |
693
|
|
|
|
|
|
|
my ($self, $type) = @_; |
694
|
|
|
|
|
|
|
my @entities; |
695
|
|
|
|
|
|
|
foreach my $entity (values %{$self->{ENTITIES}}) { |
696
|
|
|
|
|
|
|
if($entity->{TYPE} eq $type) { |
697
|
|
|
|
|
|
|
push(@entities, $entity->{NAME}); |
698
|
|
|
|
|
|
|
} |
699
|
|
|
|
|
|
|
} |
700
|
|
|
|
|
|
|
return \@entities; |
701
|
|
|
|
|
|
|
} |
702
|
|
|
|
|
|
|
|
703
|
|
|
|
|
|
|
=item I |
704
|
|
|
|
|
|
|
|
705
|
|
|
|
|
|
|
my $is_living = $anim->is_living( $entity ); |
706
|
|
|
|
|
|
|
|
707
|
|
|
|
|
|
|
Return 1 if the entity name or reference is in the animation |
708
|
|
|
|
|
|
|
and is not scheduled for deletion. Returns 0 otherwise. |
709
|
|
|
|
|
|
|
|
710
|
|
|
|
|
|
|
=cut |
711
|
|
|
|
|
|
|
sub is_living { |
712
|
|
|
|
|
|
|
my ($self, $entity) = @_; |
713
|
|
|
|
|
|
|
|
714
|
|
|
|
|
|
|
if(ref($entity) eq 'Term::Animation::Entity') { |
715
|
|
|
|
|
|
|
$entity = $entity->name(); |
716
|
|
|
|
|
|
|
} |
717
|
|
|
|
|
|
|
|
718
|
|
|
|
|
|
|
unless(exists($self->{'ENTITIES'}{$entity})) { |
719
|
|
|
|
|
|
|
return 0; |
720
|
|
|
|
|
|
|
} |
721
|
|
|
|
|
|
|
|
722
|
|
|
|
|
|
|
foreach my $dying_ent (@{$self->{DELETEQUEUE}}) { |
723
|
|
|
|
|
|
|
if($dying_ent eq $entity) { |
724
|
|
|
|
|
|
|
return 0; |
725
|
|
|
|
|
|
|
} |
726
|
|
|
|
|
|
|
} |
727
|
|
|
|
|
|
|
|
728
|
|
|
|
|
|
|
return 1; |
729
|
|
|
|
|
|
|
} |
730
|
|
|
|
|
|
|
|
731
|
|
|
|
|
|
|
=item I |
732
|
|
|
|
|
|
|
|
733
|
|
|
|
|
|
|
$entity_ref = $anim->entity( $entity_name ); |
734
|
|
|
|
|
|
|
|
735
|
|
|
|
|
|
|
If the animation contains an entity with the given name, |
736
|
|
|
|
|
|
|
the Term::Animation::Entity object associated with the name |
737
|
|
|
|
|
|
|
is returned. Otherwise, undef is returned. |
738
|
|
|
|
|
|
|
|
739
|
|
|
|
|
|
|
=cut |
740
|
|
|
|
|
|
|
sub entity { |
741
|
|
|
|
|
|
|
my ($self, $entity_name) = @_; |
742
|
|
|
|
|
|
|
if(defined($self->{ENTITIES}{$entity_name})) { |
743
|
|
|
|
|
|
|
return $self->{ENTITIES}{$entity_name}; |
744
|
|
|
|
|
|
|
} else { |
745
|
|
|
|
|
|
|
return undef; |
746
|
|
|
|
|
|
|
} |
747
|
|
|
|
|
|
|
} |
748
|
|
|
|
|
|
|
|
749
|
|
|
|
|
|
|
=item I |
750
|
|
|
|
|
|
|
|
751
|
|
|
|
|
|
|
$width = $anim->width(); |
752
|
|
|
|
|
|
|
|
753
|
|
|
|
|
|
|
Returns the width of the screen |
754
|
|
|
|
|
|
|
|
755
|
|
|
|
|
|
|
=cut |
756
|
|
|
|
|
|
|
sub width { |
757
|
|
|
|
|
|
|
my ($self) = @_; |
758
|
|
|
|
|
|
|
return $self->{WIDTH}; |
759
|
|
|
|
|
|
|
} |
760
|
|
|
|
|
|
|
|
761
|
|
|
|
|
|
|
=item I |
762
|
|
|
|
|
|
|
|
763
|
|
|
|
|
|
|
$height = $anim->height(); |
764
|
|
|
|
|
|
|
|
765
|
|
|
|
|
|
|
Returns the height of the screen |
766
|
|
|
|
|
|
|
|
767
|
|
|
|
|
|
|
=cut |
768
|
|
|
|
|
|
|
sub height { |
769
|
|
|
|
|
|
|
my ($self) = @_; |
770
|
|
|
|
|
|
|
return $self->{HEIGHT}; |
771
|
|
|
|
|
|
|
} |
772
|
|
|
|
|
|
|
|
773
|
|
|
|
|
|
|
=item I |
774
|
|
|
|
|
|
|
|
775
|
|
|
|
|
|
|
$size = $anim->size(); |
776
|
|
|
|
|
|
|
|
777
|
|
|
|
|
|
|
Returns the number of characters in the curses window (width * height) |
778
|
|
|
|
|
|
|
|
779
|
|
|
|
|
|
|
=cut |
780
|
|
|
|
|
|
|
sub size { |
781
|
|
|
|
|
|
|
my ($self) = @_; |
782
|
|
|
|
|
|
|
return ( $self->{HEIGHT} * $self->{WIDTH} ) |
783
|
|
|
|
|
|
|
} |
784
|
|
|
|
|
|
|
|
785
|
|
|
|
|
|
|
=item I |
786
|
|
|
|
|
|
|
|
787
|
|
|
|
|
|
|
$anim->redraw_screen(); |
788
|
|
|
|
|
|
|
|
789
|
|
|
|
|
|
|
Clear everything from the screen, and redraw what should be there. This |
790
|
|
|
|
|
|
|
should be called after I, or if the user indicates that |
791
|
|
|
|
|
|
|
the screen should be redrawn to get rid of artifacts. |
792
|
|
|
|
|
|
|
|
793
|
|
|
|
|
|
|
=cut |
794
|
|
|
|
|
|
|
sub redraw_screen { |
795
|
|
|
|
|
|
|
my ($self) = @_; |
796
|
|
|
|
|
|
|
$self->{WIN}->clear(); |
797
|
|
|
|
|
|
|
$self->{WIN}->refresh(); |
798
|
|
|
|
|
|
|
$self->_build_screen(); |
799
|
|
|
|
|
|
|
$self->{WIN}->move($self->{HEIGHT}-1, $self->{WIDTH}-1); |
800
|
|
|
|
|
|
|
$self->{WIN}->refresh(); |
801
|
|
|
|
|
|
|
} |
802
|
|
|
|
|
|
|
|
803
|
|
|
|
|
|
|
# draw the elements of the screen that have changed since the last update |
804
|
|
|
|
|
|
|
sub _display_screen { |
805
|
|
|
|
|
|
|
my ($self) = @_; |
806
|
|
|
|
|
|
|
$self->{WIN}->move($self->{HEIGHT}-1, $self->{WIDTH}-1); |
807
|
|
|
|
|
|
|
$self->{WIN}->refresh(); |
808
|
|
|
|
|
|
|
} |
809
|
|
|
|
|
|
|
|
810
|
|
|
|
|
|
|
|
811
|
|
|
|
|
|
|
=item I |
812
|
|
|
|
|
|
|
|
813
|
|
|
|
|
|
|
# gen_path (x,y,z, x,y,z, [ frame_pattern ], [ steps ]) |
814
|
|
|
|
|
|
|
|
815
|
|
|
|
|
|
|
$anim->gen_path( $x1, $y1, $z1, $x2, $y2, $z2, [ 1, 2, 0, 2 ], 'longest' ); |
816
|
|
|
|
|
|
|
|
817
|
|
|
|
|
|
|
Given beginning and end points, this will return a path for the |
818
|
|
|
|
|
|
|
entity to follow that can be given to the default callback routine, |
819
|
|
|
|
|
|
|
I. The first set of x,y,z coordinates are the point |
820
|
|
|
|
|
|
|
the entity will begin at, the second set is the point the entity |
821
|
|
|
|
|
|
|
will end at. |
822
|
|
|
|
|
|
|
|
823
|
|
|
|
|
|
|
You can optionally supply a list of frames to cycle through. The list |
824
|
|
|
|
|
|
|
will be repeated as many times as needed to finish the path. If no |
825
|
|
|
|
|
|
|
list of frames is supplied, only the first frame will be used. |
826
|
|
|
|
|
|
|
|
827
|
|
|
|
|
|
|
You can also request the number of steps you would like for the entity |
828
|
|
|
|
|
|
|
to take to finish the path. The default is 'shortest'. |
829
|
|
|
|
|
|
|
Valid arguments are: |
830
|
|
|
|
|
|
|
longest The longer of the X and Y distances |
831
|
|
|
|
|
|
|
shortest The shorter of the X and Y distances |
832
|
|
|
|
|
|
|
X,Y or Z The x, y or z distance |
833
|
|
|
|
|
|
|
Explicitly specify the number of steps to take |
834
|
|
|
|
|
|
|
|
835
|
|
|
|
|
|
|
=cut |
836
|
|
|
|
|
|
|
sub gen_path { |
837
|
|
|
|
|
|
|
my ($self, $x_start, $y_start, $z_start, $x_end, $y_end, $z_end, $frame_pattern, $steps_req) = @_; |
838
|
|
|
|
|
|
|
my @path = (); |
839
|
|
|
|
|
|
|
my $steps; |
840
|
|
|
|
|
|
|
|
841
|
|
|
|
|
|
|
my $x_dis = $x_end - $x_start; |
842
|
|
|
|
|
|
|
my $y_dis = $y_end - $y_start; |
843
|
|
|
|
|
|
|
my $z_dis = $z_end - $z_start; |
844
|
|
|
|
|
|
|
|
845
|
|
|
|
|
|
|
unless(defined($frame_pattern)) { |
846
|
|
|
|
|
|
|
$frame_pattern = [ 0 ]; |
847
|
|
|
|
|
|
|
} |
848
|
|
|
|
|
|
|
|
849
|
|
|
|
|
|
|
# default path length if none specified |
850
|
|
|
|
|
|
|
unless(defined($steps_req)) { |
851
|
|
|
|
|
|
|
$steps_req = 'shortest'; |
852
|
|
|
|
|
|
|
} |
853
|
|
|
|
|
|
|
|
854
|
|
|
|
|
|
|
if($steps_req eq 'shortest' or $steps_req eq 'longest') { |
855
|
|
|
|
|
|
|
if($x_dis == $y_dis) { $steps = $y_dis; } |
856
|
|
|
|
|
|
|
elsif($x_dis == 0) { $steps = $y_dis; } |
857
|
|
|
|
|
|
|
elsif($y_dis == 0) { $steps = $x_dis; } |
858
|
|
|
|
|
|
|
elsif(abs($x_dis) < abs($y_dis)) { |
859
|
|
|
|
|
|
|
if($steps_req eq 'shortest') { $steps = $x_dis; } |
860
|
|
|
|
|
|
|
else { $steps = $y_dis; } |
861
|
|
|
|
|
|
|
} else { |
862
|
|
|
|
|
|
|
if($steps_req eq 'shortest') { $steps = $y_dis; } |
863
|
|
|
|
|
|
|
else { $steps = $x_dis; } |
864
|
|
|
|
|
|
|
} |
865
|
|
|
|
|
|
|
} |
866
|
|
|
|
|
|
|
elsif($steps_req =~ /^\d+$/) { $steps = $steps_req; } |
867
|
|
|
|
|
|
|
elsif(uc($steps_req) eq 'X') { $steps = $x_dis; } |
868
|
|
|
|
|
|
|
elsif(uc($steps_req) eq 'Y') { $steps = $y_dis; } |
869
|
|
|
|
|
|
|
elsif(uc($steps_req) eq 'Z') { $steps = $z_dis; } |
870
|
|
|
|
|
|
|
else { |
871
|
|
|
|
|
|
|
carp("Unknown path length method: $steps_req"); return(); |
872
|
|
|
|
|
|
|
} |
873
|
|
|
|
|
|
|
|
874
|
|
|
|
|
|
|
$steps = abs($steps); |
875
|
|
|
|
|
|
|
|
876
|
|
|
|
|
|
|
if($steps == 0) { carp("Cannot create a zero length path!"); return (); } |
877
|
|
|
|
|
|
|
elsif($steps == 1) { |
878
|
|
|
|
|
|
|
# a path length of one is a special case where we just move from the origin to the destination |
879
|
|
|
|
|
|
|
$path[0] = [($x_end - $x_start), ($y_end - $y_start), ($z_end - $z_start), $frame_pattern->[0]]; |
880
|
|
|
|
|
|
|
return \@path; |
881
|
|
|
|
|
|
|
} |
882
|
|
|
|
|
|
|
|
883
|
|
|
|
|
|
|
my $x_incr = $x_dis / $steps; |
884
|
|
|
|
|
|
|
my $y_incr = $y_dis / $steps; |
885
|
|
|
|
|
|
|
my $z_incr = $z_dis / $steps; |
886
|
|
|
|
|
|
|
|
887
|
|
|
|
|
|
|
my ($x_pos, $y_pos, $z_pos) = ($x_start, $y_start, $z_start); |
888
|
|
|
|
|
|
|
my ($x_act, $y_act, $z_act) = ($x_start, $y_start, $z_start); |
889
|
|
|
|
|
|
|
|
890
|
|
|
|
|
|
|
for(0..$steps-2) { |
891
|
|
|
|
|
|
|
my ($x_prev, $y_prev, $z_prev) = ($x_pos, $y_pos, $z_pos); |
892
|
|
|
|
|
|
|
|
893
|
|
|
|
|
|
|
$x_pos+=$x_incr; $y_pos+=$y_incr; $z_pos+=$z_incr; |
894
|
|
|
|
|
|
|
my $f_pos = $frame_pattern->[${_}%($#{$frame_pattern}+1)]; |
895
|
|
|
|
|
|
|
|
896
|
|
|
|
|
|
|
my ($x_mov, $y_mov, $z_mov) = (int($x_pos) - int($x_prev), int($y_pos) - int($y_prev), int($z_pos) - int($z_prev)); |
897
|
|
|
|
|
|
|
$x_act += $x_mov; $y_act += $y_mov; $z_act += $z_mov; |
898
|
|
|
|
|
|
|
|
899
|
|
|
|
|
|
|
$path[$_] = [$x_mov, $y_mov, $z_mov, $f_pos]; |
900
|
|
|
|
|
|
|
} |
901
|
|
|
|
|
|
|
|
902
|
|
|
|
|
|
|
# through rounding errors, we might end up with a final position that is off by one from |
903
|
|
|
|
|
|
|
# what we actually wanted. ending up in the right place is the most important thing, |
904
|
|
|
|
|
|
|
# so we just set the final position to put us where we want to be |
905
|
|
|
|
|
|
|
$path[$steps-1] = [$x_end - $x_act, $y_end - $y_act, $z_end - $z_act, $frame_pattern->[($steps - 1)%($#{$frame_pattern}+1)]]; |
906
|
|
|
|
|
|
|
|
907
|
|
|
|
|
|
|
return \@path; |
908
|
|
|
|
|
|
|
} |
909
|
|
|
|
|
|
|
|
910
|
|
|
|
|
|
|
|
911
|
|
|
|
|
|
|
# run the callback routines for all entities that have them, and update |
912
|
|
|
|
|
|
|
# the entity accordingly. also checks for auto death status |
913
|
|
|
|
|
|
|
sub _do_callbacks { |
914
|
|
|
|
|
|
|
my ($self) = @_; |
915
|
|
|
|
|
|
|
|
916
|
|
|
|
|
|
|
foreach my $entity (keys %{$self->{ENTITIES}}) { |
917
|
|
|
|
|
|
|
my $ent = $self->{ENTITIES}{$entity}; |
918
|
|
|
|
|
|
|
|
919
|
|
|
|
|
|
|
# check for methods to automatically die |
920
|
|
|
|
|
|
|
if(defined($ent->{'DIE_TIME'}) and $ent->{'DIE_TIME'} <= time()) { |
921
|
|
|
|
|
|
|
del_entity($self, $entity); next; |
922
|
|
|
|
|
|
|
} |
923
|
|
|
|
|
|
|
|
924
|
|
|
|
|
|
|
if(defined($ent->{'DIE_FRAME'}) and ($ent->{'DIE_FRAME'}--) <= 0) { |
925
|
|
|
|
|
|
|
del_entity($self, $entity); next; |
926
|
|
|
|
|
|
|
} |
927
|
|
|
|
|
|
|
|
928
|
|
|
|
|
|
|
if(defined($ent->{'DIE_ENTITY'}) and !$self->is_living($ent->{'DIE_ENTITY'}) ) { |
929
|
|
|
|
|
|
|
del_entity($self, $entity); next; |
930
|
|
|
|
|
|
|
} |
931
|
|
|
|
|
|
|
|
932
|
|
|
|
|
|
|
if($ent->{'DIE_OFFSCREEN'}) { |
933
|
|
|
|
|
|
|
if($ent->{X} >= $self->{WIDTH} or $ent->{Y} >= $self->{HEIGHT} or |
934
|
|
|
|
|
|
|
$ent->{X} < (0 - $ent->{WIDTH}) or $ent->{Y} < (0 - $ent->{HEIGHT})) { |
935
|
|
|
|
|
|
|
del_entity($self, $entity); next; |
936
|
|
|
|
|
|
|
} |
937
|
|
|
|
|
|
|
} |
938
|
|
|
|
|
|
|
|
939
|
|
|
|
|
|
|
if(defined($ent->{CALLBACK})) { |
940
|
|
|
|
|
|
|
my ($x, $y, $z, $f) = $ent->{CALLBACK}->($ent, $self); |
941
|
|
|
|
|
|
|
if(defined($x)) { |
942
|
|
|
|
|
|
|
if($ent->{WRAP}) { |
943
|
|
|
|
|
|
|
if($x >= $self->{WIDTH}) { $x = ($x - int($x)) + ($x % $self->{WIDTH}); } |
944
|
|
|
|
|
|
|
elsif($x < 0) { $x = ($x - int($x)) + ($x % $self->{WIDTH}); } |
945
|
|
|
|
|
|
|
} |
946
|
|
|
|
|
|
|
$ent->{X} = $x; |
947
|
|
|
|
|
|
|
} |
948
|
|
|
|
|
|
|
if(defined($y)) { |
949
|
|
|
|
|
|
|
if($ent->{WRAP}) { |
950
|
|
|
|
|
|
|
if($y >= $self->{HEIGHT}) { $y = ($y - int($y)) + ($y % $self->{HEIGHT}); } |
951
|
|
|
|
|
|
|
elsif($y < 0) { $y = ($y - int($y)) + ($y % $self->{HEIGHT}); } |
952
|
|
|
|
|
|
|
} |
953
|
|
|
|
|
|
|
$ent->{Y} = $y; |
954
|
|
|
|
|
|
|
} |
955
|
|
|
|
|
|
|
$ent->{Z} = defined($z) ? $z : $ent->{Z}; |
956
|
|
|
|
|
|
|
$ent->{CURR_FRAME} = defined($f) ? $f : $ent->{CURR_FRAME}; |
957
|
|
|
|
|
|
|
|
958
|
|
|
|
|
|
|
} |
959
|
|
|
|
|
|
|
} |
960
|
|
|
|
|
|
|
} |
961
|
|
|
|
|
|
|
|
962
|
|
|
|
|
|
|
# called after all other updates. moves any entities that |
963
|
|
|
|
|
|
|
# follow another entity |
964
|
|
|
|
|
|
|
sub _move_followers { |
965
|
|
|
|
|
|
|
my ($self) = @_; |
966
|
|
|
|
|
|
|
|
967
|
|
|
|
|
|
|
foreach my $entity_name (keys %{$self->{ENTITIES}}) { |
968
|
|
|
|
|
|
|
my $follower = $self->{ENTITIES}{$entity_name}; |
969
|
|
|
|
|
|
|
|
970
|
|
|
|
|
|
|
next unless(defined($follower->{FOLLOW_ENTITY})); |
971
|
|
|
|
|
|
|
|
972
|
|
|
|
|
|
|
my $leader = $self->entity($follower->{FOLLOW_ENTITY}); |
973
|
|
|
|
|
|
|
next unless(defined($leader)); |
974
|
|
|
|
|
|
|
my $offset = $follower->{FOLLOW_OFFSET}; |
975
|
|
|
|
|
|
|
|
976
|
|
|
|
|
|
|
if(defined($offset->[0])) { $follower->x( $offset->[0] + $leader->x ); } |
977
|
|
|
|
|
|
|
if(defined($offset->[1])) { $follower->y( $offset->[1] + $leader->y ); } |
978
|
|
|
|
|
|
|
if(defined($offset->[2])) { $follower->z( $offset->[2] + $leader->z ); } |
979
|
|
|
|
|
|
|
if(defined($offset->[3])) { $follower->frame( $offset->[3] + $leader->frame ); } |
980
|
|
|
|
|
|
|
} |
981
|
|
|
|
|
|
|
} |
982
|
|
|
|
|
|
|
|
983
|
|
|
|
|
|
|
sub _collision_handlers { |
984
|
|
|
|
|
|
|
my ($self) = @_; |
985
|
|
|
|
|
|
|
foreach my $entity (values %{$self->{ENTITIES}}) { |
986
|
|
|
|
|
|
|
if(defined($entity->{COLL_HANDLER}) && defined($entity->{COLLISIONS})) { |
987
|
|
|
|
|
|
|
$entity->{COLL_HANDLER}->($entity, $self); |
988
|
|
|
|
|
|
|
} |
989
|
|
|
|
|
|
|
$entity->{COLLISIONS} = (); |
990
|
|
|
|
|
|
|
} |
991
|
|
|
|
|
|
|
} |
992
|
|
|
|
|
|
|
|
993
|
|
|
|
|
|
|
=item I |
994
|
|
|
|
|
|
|
|
995
|
|
|
|
|
|
|
$anim->end(); |
996
|
|
|
|
|
|
|
|
997
|
|
|
|
|
|
|
Run the Curses endwin function to get your terminal back to its |
998
|
|
|
|
|
|
|
normal mode. This is called automatically when the object is |
999
|
|
|
|
|
|
|
destroyed if the animation is running full screen (if you |
1000
|
|
|
|
|
|
|
did not pass an existing Curses window to the constructor). |
1001
|
|
|
|
|
|
|
|
1002
|
|
|
|
|
|
|
=cut |
1003
|
|
|
|
|
|
|
sub end { |
1004
|
|
|
|
|
|
|
my ($self) = @_; |
1005
|
|
|
|
|
|
|
endwin; |
1006
|
|
|
|
|
|
|
} |
1007
|
|
|
|
|
|
|
|
1008
|
|
|
|
|
|
|
# write to a log file, for debugging |
1009
|
|
|
|
|
|
|
sub _elog { |
1010
|
|
|
|
|
|
|
my ($mesg) = @_; |
1011
|
|
|
|
|
|
|
open(F, ">>", "elog.log"); |
1012
|
|
|
|
|
|
|
print F "$mesg\n"; |
1013
|
|
|
|
|
|
|
close(F); |
1014
|
|
|
|
|
|
|
} |
1015
|
|
|
|
|
|
|
|
1016
|
|
|
|
|
|
|
1; |
1017
|
|
|
|
|
|
|
|
1018
|
|
|
|
|
|
|
=back |
1019
|
|
|
|
|
|
|
|
1020
|
|
|
|
|
|
|
=head1 CALLBACK ROUTINES |
1021
|
|
|
|
|
|
|
|
1022
|
|
|
|
|
|
|
Callback routines for all entities are called each time I |
1023
|
|
|
|
|
|
|
is called. A default callback routine is supplied, I, which |
1024
|
|
|
|
|
|
|
is sufficient for most basic movement. If you want to create an entity |
1025
|
|
|
|
|
|
|
that exhibits more complex behavior, you will have to write a custom |
1026
|
|
|
|
|
|
|
callback routine for it. |
1027
|
|
|
|
|
|
|
|
1028
|
|
|
|
|
|
|
Callback routines take two arguments, a reference to the Term::Animation::Entity |
1029
|
|
|
|
|
|
|
object that it should act on, and a reference to the Term::Animation instance |
1030
|
|
|
|
|
|
|
that called it. Any arguments required to tell the callback what to do with |
1031
|
|
|
|
|
|
|
the object, or any state that needs to be maintained, should be put |
1032
|
|
|
|
|
|
|
in the I element of the object. I is only |
1033
|
|
|
|
|
|
|
referenced by the callback routine, and thus can contain any datastructure |
1034
|
|
|
|
|
|
|
that you find useful. |
1035
|
|
|
|
|
|
|
|
1036
|
|
|
|
|
|
|
Here is an example custom callback that will make an entity move randomly |
1037
|
|
|
|
|
|
|
around the screen: |
1038
|
|
|
|
|
|
|
|
1039
|
|
|
|
|
|
|
sub random_movement { |
1040
|
|
|
|
|
|
|
my ($entity, $anim) = @_; |
1041
|
|
|
|
|
|
|
|
1042
|
|
|
|
|
|
|
# get the current position of the entity |
1043
|
|
|
|
|
|
|
my ($x, $y, $z) = $entity->position(); |
1044
|
|
|
|
|
|
|
|
1045
|
|
|
|
|
|
|
# we'll use callback_args to store the last axis we moved in |
1046
|
|
|
|
|
|
|
my $last_move = $entity->callback_args(); |
1047
|
|
|
|
|
|
|
|
1048
|
|
|
|
|
|
|
# if we moved in x last time, move in y this time |
1049
|
|
|
|
|
|
|
if($last_move eq 'x') { |
1050
|
|
|
|
|
|
|
$entity->callback_args('y'); |
1051
|
|
|
|
|
|
|
# move by -1, 0 or 1 |
1052
|
|
|
|
|
|
|
$y += int(rand(3)) - 1; |
1053
|
|
|
|
|
|
|
} else { |
1054
|
|
|
|
|
|
|
$entity->callback_args('x'); |
1055
|
|
|
|
|
|
|
$x += int(rand(3)) - 1; |
1056
|
|
|
|
|
|
|
} |
1057
|
|
|
|
|
|
|
|
1058
|
|
|
|
|
|
|
# return the absolute x,y,z coordinates to move to |
1059
|
|
|
|
|
|
|
return ($x, $y, $z); |
1060
|
|
|
|
|
|
|
} |
1061
|
|
|
|
|
|
|
|
1062
|
|
|
|
|
|
|
The return value of your callback routine should be of the form: |
1063
|
|
|
|
|
|
|
|
1064
|
|
|
|
|
|
|
return ($x, $y, $z, $frame) |
1065
|
|
|
|
|
|
|
|
1066
|
|
|
|
|
|
|
$x, $y and $z represent the X, Y and Z coordinates to which the object |
1067
|
|
|
|
|
|
|
should move. $frame is the frame number that the object should display, |
1068
|
|
|
|
|
|
|
if it has multiple frames of animation. Any values that are unspecified |
1069
|
|
|
|
|
|
|
or undef will remain unchanged. |
1070
|
|
|
|
|
|
|
|
1071
|
|
|
|
|
|
|
You can also call the default callback from within your callback, if |
1072
|
|
|
|
|
|
|
you want it to handle movement for you. For example, if your callback |
1073
|
|
|
|
|
|
|
is simply used to decide when an entity should die: |
1074
|
|
|
|
|
|
|
|
1075
|
|
|
|
|
|
|
sub wait_for_file { |
1076
|
|
|
|
|
|
|
my ($entity, $anim) = @_; |
1077
|
|
|
|
|
|
|
|
1078
|
|
|
|
|
|
|
# kill this entity if a certain file shows up |
1079
|
|
|
|
|
|
|
if(-e "/path/to/file") { |
1080
|
|
|
|
|
|
|
$entity->kill(); |
1081
|
|
|
|
|
|
|
return(); |
1082
|
|
|
|
|
|
|
} |
1083
|
|
|
|
|
|
|
|
1084
|
|
|
|
|
|
|
# use the default callback to handle the actual movement |
1085
|
|
|
|
|
|
|
return $entity->move_entity($anim); |
1086
|
|
|
|
|
|
|
} |
1087
|
|
|
|
|
|
|
|
1088
|
|
|
|
|
|
|
If you use this, be aware that I relies on |
1089
|
|
|
|
|
|
|
I, so you cannot use it to store your own |
1090
|
|
|
|
|
|
|
arbitrary data. |
1091
|
|
|
|
|
|
|
|
1092
|
|
|
|
|
|
|
=head1 COLOR |
1093
|
|
|
|
|
|
|
|
1094
|
|
|
|
|
|
|
ANSI color is available for terminals that support it. Only a single |
1095
|
|
|
|
|
|
|
background color can be used for the window (it would look terrible |
1096
|
|
|
|
|
|
|
in most cases otherwise anyway). Colors for entities are specified by |
1097
|
|
|
|
|
|
|
using a 'mask' that indicates the color for each character. For |
1098
|
|
|
|
|
|
|
example, say we had a single frame of a bird: |
1099
|
|
|
|
|
|
|
|
1100
|
|
|
|
|
|
|
$bird = q# |
1101
|
|
|
|
|
|
|
|
1102
|
|
|
|
|
|
|
---. .-. .--- |
1103
|
|
|
|
|
|
|
--\'v'/-- |
1104
|
|
|
|
|
|
|
\ / |
1105
|
|
|
|
|
|
|
" " |
1106
|
|
|
|
|
|
|
#; |
1107
|
|
|
|
|
|
|
|
1108
|
|
|
|
|
|
|
To indicate the colors you want to use for the bird, create a matching |
1109
|
|
|
|
|
|
|
mask, with the first letter of each color in the appropriate position |
1110
|
|
|
|
|
|
|
(except black, which is 'k'). Pass this mask as the I parameter. |
1111
|
|
|
|
|
|
|
|
1112
|
|
|
|
|
|
|
$mask = q# |
1113
|
|
|
|
|
|
|
|
1114
|
|
|
|
|
|
|
BBBB BBB BBBB |
1115
|
|
|
|
|
|
|
BBBWYWBBB |
1116
|
|
|
|
|
|
|
B B |
1117
|
|
|
|
|
|
|
Y Y |
1118
|
|
|
|
|
|
|
#; |
1119
|
|
|
|
|
|
|
|
1120
|
|
|
|
|
|
|
When specifying a color, using uppercase indicates the color should be |
1121
|
|
|
|
|
|
|
bold. So 'BLUE' or 'B' means bold blue, and 'blue' or 'b' means non-bold |
1122
|
|
|
|
|
|
|
blue. 'Blue' means you get an error message. |
1123
|
|
|
|
|
|
|
|
1124
|
|
|
|
|
|
|
You can also provide a default color with the default_color parameter. |
1125
|
|
|
|
|
|
|
This color will be used for any character that does |
1126
|
|
|
|
|
|
|
not have an entry in the mask. If you want the entire entity to be |
1127
|
|
|
|
|
|
|
a single color, you can just provide a default color with no mask. |
1128
|
|
|
|
|
|
|
|
1129
|
|
|
|
|
|
|
The available colors are: red, green, blue, cyan, magenta, yellow, black |
1130
|
|
|
|
|
|
|
and white. |
1131
|
|
|
|
|
|
|
|
1132
|
|
|
|
|
|
|
Here's an example call to build_object for the bird above. |
1133
|
|
|
|
|
|
|
|
1134
|
|
|
|
|
|
|
$anim->new_entity ( |
1135
|
|
|
|
|
|
|
name => "Bird", |
1136
|
|
|
|
|
|
|
shape => $bird, |
1137
|
|
|
|
|
|
|
position => [ 5, 8, 7 ], |
1138
|
|
|
|
|
|
|
callback_args => [ 1, 2, 0, 0 ], |
1139
|
|
|
|
|
|
|
color => $mask, |
1140
|
|
|
|
|
|
|
default_color => "BLUE" |
1141
|
|
|
|
|
|
|
); |
1142
|
|
|
|
|
|
|
|
1143
|
|
|
|
|
|
|
=head1 AUTHOR |
1144
|
|
|
|
|
|
|
|
1145
|
|
|
|
|
|
|
Kirk Baucom, Ekbaucom@schizoid.comE |
1146
|
|
|
|
|
|
|
|
1147
|
|
|
|
|
|
|
=head1 SEE ALSO |
1148
|
|
|
|
|
|
|
|
1149
|
|
|
|
|
|
|
L |
1150
|
|
|
|
|
|
|
|
1151
|
|
|
|
|
|
|
=cut |