2
#*****************************************************************************
6
# Copyright (c) 2000, 2001, 2002 Guillaume Cottenceau <guillaume.cottenceau at free.fr>
8
# Sponsored by MandrakeSoft <http://www.mandrakesoft.com/>
10
# This program is free software; you can redistribute it and/or modify
11
# it under the terms of the GNU General Public License version 2, as
12
# published by the Free Software Foundation.
14
# This program is distributed in the hope that it will be useful,
15
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
# GNU General Public License for more details.
19
# You should have received a copy of the GNU General Public License
20
# along with this program; if not, write to the Free Software
21
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24
#******************************************************************************
26
# Design & Programming by Guillaume Cottenceau between Oct 2001 and Jan 2002.
28
# Check official home: http://www.frozen-bubble.org/
30
#******************************************************************************
33
# Yes it uses Perl, you non-believer :-).
39
use vars qw($TARGET_ANIM_SPEED $BUBBLE_SIZE $ROW_SIZE $LAUNCHER_SPEED $BUBBLE_SPEED $MALUS_BUBBLE_SPEED $TIME_APPEARS_NEW_ROOT %POS %POS_1P %POS_2P $KEYS %actions %angle %pdata $app $font %apprects $event %rects %sticked_bubbles %root_bubbles $background $background_orig @bubbles_images $gcwashere %bubbles_anim %launched_bubble %tobe_launched %next_bubble $shooter $sdl_flags $mixer %sound %music %pinguin %canon $graphics_level @update_rects $CANON_ROTATIONS_NB %malus_bubble %falling_bubble %exploding_bubble %malus_gfx %sticking_bubble $version $time %imgbin $TIME_HURRY_WARN $TIME_HURRY_MAX $TIMEOUT_PINGUIN_SLEEP $FREE_FALL_CONSTANT $direct @PLAYERS %levels $display_on_app_disabled $total_time $time_1pgame $fullscreen $rcfile $hiscorefile $HISCORES $PI $FPATH);
43
use fbmdkcommon; #- should be MDK::Common;
55
$FPATH = '@PREFIX@/share/frozen-bubble';
59
$TARGET_ANIM_SPEED = 20; # number of milliseconds that should last between two animation frames
61
$LAUNCHER_SPEED = 0.03; # speed of rotation of launchers
62
$BUBBLE_SPEED = 10; # speed of movement of launched bubbles
63
$MALUS_BUBBLE_SPEED = 30; # speed of movement of "malus" launched bubbles
64
$CANON_ROTATIONS_NB = 40; # number of rotations of images for canon (should be consistent with gfx/shoot/Makefile)
66
%POS_2P = ( p1 => { left_limit => 30, right_limit => 286, pinguin => { x => 168, 'y' => 437 }, malus_x => 305, scores_x => 293 },
67
p2 => { left_limit => 354, right_limit => 610, pinguin => { x => 32, 'y' => 437 }, malus_x => 328, scores_x => 341 },
69
'initial_bubble_y' => 390,
70
next_bubble => { x => 112, 'y' => 440 },
72
hurry => { x => 10, 'y' => 265 },
73
centerpanel => { x => 153, 'y' => 190 },
77
%POS_1P = ( p1 => { left_limit => 190, right_limit => 446, pinguin => { x => 168, 'y' => 437 }, scores_x => 180 },
79
'initial_bubble_y' => 390,
80
next_bubble => { x => 112, 'y' => 440 },
82
hurry => { x => 10, 'y' => 265 },
83
centerpanel => { x => 153, 'y' => 190 },
85
compressor_xpos => 321,
88
$ROW_SIZE = $BUBBLE_SIZE * 7/8;
89
$TIMEOUT_PINGUIN_SLEEP = 200;
90
$FREE_FALL_CONSTANT = 0.5;
91
$KEYS = { p1 => { left => SDLK_x, right => SDLK_v, fire => SDLK_c },
92
p2 => { left => SDLK_LEFT, right => SDLK_RIGHT, fire => SDLK_UP } };
94
$sdl_flags = SDL_ANYFORMAT | SDL_HWSURFACE | SDL_DOUBLEBUF | SDL_HWACCEL | SDL_ASYNCBLIT;
99
$rcfile = "$ENV{HOME}/.fbrc";
100
-r $rcfile and eval(cat_($rcfile));
101
$hiscorefile = "$ENV{HOME}/.fbhighscores";
102
eval(cat_(-r $hiscorefile ? $hiscorefile : "$FPATH/highscores.default"));
106
$ENV{LD_PRELOAD} !~ '@PREFIX@/lib/frozen-bubble/libSDL_mixer-1.2.so.0' and die "Please run `frozen-bubble' to start the game\n";
108
print " [[ Frozen-Bubble-$version ]]\n\n";
109
print ' http://www.frozen-bubble.org/
111
Copyright (c) 2000, 2001, 2002 Guillaume Cottenceau.
112
Artwork: Alexis Younes <73lab at free.fr>
113
Amaury Amblard-Ladurantie <amaury at linuxfr.org>
114
Soundtrack: Matthias Le Bidan <matthias.le_bidan at caramail.com>
115
Design & Programming: Guillaume Cottenceau <guillaume.cottenceau at free.fr>
117
Sponsored by MandrakeSoft <http://www.mandrakesoft.com/>
119
This program is free software; you can redistribute it and/or modify
120
it under the terms of the GNU General Public License version 2, as
121
published by the Free Software Foundation.
125
local $_ = join '', @ARGV;
127
/-h/ and die "usage: $0 [-h] [-fs --fullscreen] [-ns --nosound] [-sl --slow_machine] [-vs --very_slow_machine] [-so --solo] [-l#n] [-di --direct]\n";
128
/-fs/ || /-fullscreen/ and $fullscreen = 1;
129
/-ns/ || /-nosound/ and $mixer = 'SOUND_DISABLED';
130
/-sl/ and $graphics_level = 2;
131
/-vs/ || /-very_slow_machine/ and $graphics_level = 1;
132
/-srand/ and srand 0;
133
/-di/ and $direct = 1;
134
/-so/ and @PLAYERS = ('p1');
135
/-l(\d+)/ and $levels{current} = $1;
138
#- ------------------------------------------------------------------------
142
$total_time = ($app->ticks() - $total_time)/1000;
143
my $h = int($total_time/3600);
144
my $m = int(($total_time-$h*3600)/60);
145
my $s = int($total_time-$h*3600-$m*60);
146
print "\nAddicted during ", $h ? "$h"."h " : "", $m ? "$m"."m " : "", "$s"."s.\n";
148
# fb_c_stuff::_exit(0); #- so that process will not segfault (probably in atexit), if the sound has been disabled in the menu
151
#- it doesn't keep ordering (but I don't care)
152
sub fastuniq { my %l; @l{@_} = @_; values %l }
155
#- sdlpl-1.12 is bugged for SDL::Surface::display_format :-(
156
sub my_display_format {
158
my $tmp = SDL::sdlpl::sdl_display_format($surface->{-surface});
159
SDL::sdlpl::sdl_free_surface($surface->{-surface});
160
$surface->{-surface} = $tmp;
164
#- ----------- sound related stuff ----------------------------------------
167
$mixer and $sound{$_[0]} and $mixer->play_channel(-1, $sound{$_[0]}, 0);
170
sub play_music($;$) {
171
my ($name, $pos) = @_;
173
$mixer and $mixer->music_paused() and $mixer->resume_music();
174
$app->delay(10) while $mixer->fading_music(); #- mikmod will deadlock if we try to fade_out while still fading in
175
$mixer->playing_music() and $mixer->fade_out_music(500); $app->delay(400);
176
$app->delay(10) while $mixer->playing_music(); #- mikmod will segfault if we try to load a music while old one is still fading out
177
my %musics = (intro => '/snd/introzik.xm', main1p => '/snd/frozen-mainzik-1p.xm', main2p => '/snd/frozen-mainzik-2p.xm');
178
my $mus if 0; #- I need to keep a reference on the music or it will be collected at the end of this function, thus I manually collect previous music
179
$mus = new SDL::Music("$FPATH$musics{$name}");
180
$mus->{-data} or print STDERR "Warning, could not create new music `$name' from `$musics{$name}'.\n", $app->error();
182
fb_c_stuff::fade_in_music_position($mus->{-data}, -1, 500, $pos);
184
$mixer->play_music($mus, -1);
189
$mixer = eval { new SDL::Mixer(-frequency => 44100, -format => AUDIO_S16, -channels => 2, -size => 1024); };
191
$@ =~ s| at \S+ line.*\n||;
192
print STDERR "\nWarning: can't initialize sound (reason: $@).\n";
195
print "[Sound Init]\n";
196
my @sounds = qw(stick destroy_group newroot newroot_solo lose hurry pause menu_change menu_selected rebound launch malus noh snore cancel typewriter applause);
198
my $sound_path = "$FPATH/snd/$_.wav";
199
$sound{$_} = new SDL::Sound($sound_path);
200
if ($sound{$_}{-data}) {
201
$sound{$_}->volume(80);
203
print STDERR "Warning, could not create new sound from `$sound_path'.\n";
210
#- ----------- graphics related stuff --------------------------------------
212
sub add_default_rect($) {
214
$rects{$surface} = new SDL::Rect(-width => $surface->width, -height => $surface->height);
218
my ($image, $x, $y) = @_;
219
($x == 0 && $y == 0) and print "put_image: warning, X and Y are 0\n";
220
$rects{$image} or die "please don't call me with no rects\n".backtrace();
221
my $drect = new SDL::Rect(-width => $image->width, -height => $image->height, -x => $x, '-y' => $y);
222
$image->blit($rects{$image}, $app, $drect);
223
push @update_rects, $drect;
226
sub erase_image_from($$$$) {
227
my ($image, $x, $y, $img) = @_;
228
my $drect = new SDL::Rect(-width => $image->width, -height => $image->height, -x => $x, '-y' => $y);
229
$img->blit($drect, $app, $drect);
230
push @update_rects, $drect;
233
sub erase_image($$$) {
234
my ($image, $x, $y) = @_;
235
erase_image_from($image, $x, $y, $background);
238
sub put_image_to_background($$$) {
239
my ($image, $x, $y) = @_;
241
($x == 0 && $y == 0) and print "put_image_to_background: warning, X and Y are 0\n";
243
$drect = new SDL::Rect(-width => $image->width, -height => $image->height, -x => $x, '-y' => $y);
244
$display_on_app_disabled or $image->blit($rects{$image}, $app, $drect);
245
$image->blit($rects{$image}, $background, $drect);
246
} else { #- clipping seems to not work when from one Surface to another Surface, so I need to do clipping by hand
247
$drect = new SDL::Rect(-width => $image->width, -height => $image->height + $y, -x => $x, '-y' => 0);
248
my $irect = new SDL::Rect(-width => $image->width, -height => $image->height + $y, '-y' => -$y);
249
$display_on_app_disabled or $image->blit($irect, $app, $drect);
250
$image->blit($irect, $background, $drect);
252
push @update_rects, $drect;
255
sub remove_image_from_background($$$) {
256
my ($image, $x, $y) = @_;
257
($x == 0 && $y == 0) and print "remove_image_from_background: warning, X and Y are 0\n";
258
my $drect = new SDL::Rect(-width => $image->width, -height => $image->height, -x => $x, '-y' => $y);
259
$background_orig->blit($drect, $background, $drect);
260
$background_orig->blit($drect, $app, $drect);
261
push @update_rects, $drect;
264
sub remove_images_from_background {
265
my ($player, @images) = @_;
267
($_->{'x'} == 0 && $_->{'y'} == 0) and print "remove_images_from_background: warning, X and Y are 0\n";
268
my $drect = new SDL::Rect(-width => $_->{img}->width, -height => $_->{img}->height, -x => $_->{'x'}, '-y' => $_->{'y'});
269
$background_orig->blit($drect, $background, $drect);
270
$background_orig->blit($drect, $app, $drect);
271
push @update_rects, $drect;
275
sub put_allimages_to_background($) {
277
put_image_to_background($_->{img}, $_->{'x'}, $_->{'y'}) foreach @{$sticked_bubbles{$player}};
280
sub switch_image_on_background($$$;$) {
281
my ($image, $x, $y, $save) = @_;
282
($x == 0 && $y == 0) and print "put_image: warning, X and Y are 0\n";
283
my $drect = new SDL::Rect(-width => $image->width, -height => $image->height, -x => $x, '-y' => $y);
285
$save = new SDL::Surface(-width => $image->width, -height => $image->height, -depth => 32, -Amask => "0 but true"); #- grrr... this piece of shit of Amask made the surfaces slightly modify along the print/erase of "Hurry" and "Pause".... took me so much time to debug and find that the problem came from a bug when Amask is set to 0xFF000000 (while it's -supposed- to be set to 0xFF000000 with 32-bit graphics!!)
286
$background->blit($drect, $save, $rects{$image});
288
$image->blit($rects{$image} || new SDL::Rect(-width => $image->width, -height => $image->height), $background, $drect);
289
$background->blit($drect, $app, $drect);
290
push @update_rects, $drect;
295
my $file = "$FPATH/gfx/$_[0]";
296
my $img = new SDL::Surface(-name => $file);
297
$img->{-surface} or die "FATAL: Couldn't load `$file' into a SDL::Surface.\n";
298
add_default_rect($img);
302
sub add_bubble_image($) {
304
my $bubble = add_image($file);
305
push @bubbles_images, $bubble;
309
#- ----------- generic game stuff -----------------------------------------
311
sub iter_players(&) {
314
foreach $::p (@PLAYERS) {
318
sub iter_players_(&) { #- so that I can do an iter_players_ from within an iter_players
321
foreach $::p_ (@PLAYERS) {
325
sub is_1p_game() { listlength(@PLAYERS) == 1 }
326
sub is_2p_game() { listlength(@PLAYERS) == 2 }
329
#- ----------- bubble game stuff ------------------------------------------
331
sub calc_real_pos($$) { # try to optimize a bit
332
my ($b, $player) = @_;
333
$b->{'x'} = $POS{$player}{left_limit} + $b->{cx} * $BUBBLE_SIZE + odd($b->{cy}+$pdata{$player}{oddswap}) * $BUBBLE_SIZE/2;
334
$b->{'y'} = $POS{top_limit} + $b->{cy} * $ROW_SIZE;
337
sub get_array_yclosest($) {
339
return int(($y-$POS{top_limit}+$ROW_SIZE/2) / $ROW_SIZE);
342
sub get_array_closest_pos($$$) { # roughly the opposite than previous function
343
my ($x, $y, $player) = @_;
344
my $ny = get_array_yclosest($y);
345
my $nx = int(($x-$POS{$player}{left_limit}+$BUBBLE_SIZE/2 - odd($ny+$pdata{$player}{oddswap})*$BUBBLE_SIZE/2)/$BUBBLE_SIZE);
349
sub is_collision($$$) {
350
my ($bub, $x, $y) = @_;
351
my $DISTANCE_COLLISION_SQRED = sqr($BUBBLE_SIZE * 0.82);
352
my $xs = sqr($bub->{x} - $x);
353
($xs > $DISTANCE_COLLISION_SQRED) and return 0;
354
return ($xs + sqr($bub->{'y'} - $y)) < $DISTANCE_COLLISION_SQRED;
357
sub create_bubble_given_img($) {
360
ref($img) eq 'SDL::Surface' or die "<$img> seems to not be a valid image\n" . backtrace();
365
sub create_bubble(;$) {
367
my $b = create_bubble_given_img($bubbles_images[rand(listlength(@bubbles_images))]);
368
is_1p_game() && $p && !member($b->{img}, map { $_->{img} } @{$sticked_bubbles{$p}})
369
and return &create_bubble($p); #- prototype checking pb w/ recursion
373
sub bubble_next_to($$$$$) {
374
my ($x1, $y1, $x2, $y2, $player) = @_;
375
$x1 == $x2 && $y1 == $y2 and die "bubble_next_to: assert failed -- same bubbles " . backtrace();
376
return to_bool((sqr($x1+odd($y1+$pdata{$player}{oddswap})*0.5 - ($x2+odd($y2+$pdata{$player}{oddswap})*0.5)) + sqr($y1 - $y2)) < 3);
379
#- bubble ends its life sticked somewhere
380
sub real_stick_bubble {
381
my ($bubble, $xpos, $ypos, $player, $neighbours_ok) = @_;
382
$bubble->{cx} = $xpos;
383
$bubble->{cy} = $ypos;
384
foreach (@{$sticked_bubbles{$player}}) {
385
if (bubble_next_to($_->{cx}, $_->{cy}, $bubble->{cx}, $bubble->{cy}, $player)) {
386
push @{$_->{neighbours}}, $bubble;
387
$neighbours_ok or push @{$bubble->{neighbours}}, $_;
390
push @{$sticked_bubbles{$player}}, $bubble;
391
$bubble->{cy} == $pdata{$player}{newrootlevel} and push @{$root_bubbles{$player}}, $bubble;
392
calc_real_pos($bubble, $player);
393
put_image_to_background($bubble->{img}, $bubble->{'x'}, $bubble->{'y'});
396
sub destroy_bubbles {
397
my ($player, @bubz) = @_;
398
$graphics_level == 1 and return;
400
$_->{speedx} = rand(3)-1.5;
401
$_->{speedy} = -rand(4)-2;
403
push @{$exploding_bubble{$player}}, @bubz;
406
sub stick_bubble($$$$) {
407
my ($bubble, $xpos, $ypos, $player) = @_;
410
my @neighbours = ($bubble);
412
@{$bubble->{neighbours}} = grep { bubble_next_to($_->{cx}, $_->{cy}, $xpos, $ypos, $player) } @{$sticked_bubbles{$player}};
414
push @will_destroy, @neighbours;
415
@neighbours = grep { $bubble->{img} eq $_->{img} && !member($_, @will_destroy) } fastuniq(map { @{$_->{neighbours}} } @neighbours);
416
last if !@neighbours;
418
shift @will_destroy; #- remove "$bubble" which is at the front of the array
420
if (listlength(@will_destroy) <= 1) {
423
real_stick_bubble($bubble, $xpos, $ypos, $player, 1);
424
$sticking_bubble{$player} = $bubble;
425
$pdata{$player}{sticking_step} = 0;
428
play_sound('destroy_group');
429
foreach my $b (difference2([ fastuniq(map { @{$_->{neighbours}} } @will_destroy) ], \@will_destroy)) {
430
@{$b->{neighbours}} = difference2($b->{neighbours}, \@will_destroy);
432
@{$sticked_bubbles{$player}} = difference2($sticked_bubbles{$player}, \@will_destroy);
433
@{$root_bubbles{$player}} = difference2($root_bubbles{$player}, \@will_destroy);
435
$bubble->{'cx'} = $xpos;
436
$bubble->{'cy'} = $ypos;
437
calc_real_pos($bubble, $player);
438
destroy_bubbles($player, @will_destroy, $bubble);
440
#- find falling bubbles
441
$_->{mark} = 0 foreach @{$sticked_bubbles{$player}};
443
my @neighbours = @{$root_bubbles{$player}};
445
$_->{mark} = 1 foreach @neighbours;
446
push @sticked, @neighbours;
447
@neighbours = grep { $_->{mark} == 0 } map { @{$_->{neighbours}} } @neighbours;
448
last if !@neighbours;
450
@falling = difference2($sticked_bubbles{$player}, \@sticked);
451
@{$sticked_bubbles{$player}} = difference2($sticked_bubbles{$player}, \@falling);
453
if ($graphics_level > 1) {
454
my $max_cy_falling = fold_left { $::b->{cy} > $::a ? $::b->{cy} : $::a } 0, @falling; #- I have a fold_left in my prog! :-)
455
my ($shift_on_same_line, $line) = (0, $max_cy_falling);
456
foreach (sort { $::b->{cy}*7+$::b->{cx} <=> $::a->{cy}*7+$::a->{cx} } @falling) { #- sort bottom-to-up / right-to-left
457
$shift_on_same_line = 0 if $line != $_->{cy};
459
$_->{wait_fall} = ($max_cy_falling - $_->{cy})*5 + $shift_on_same_line;
460
$shift_on_same_line++;
463
push @{$falling_bubble{$player}}, @falling;
466
remove_images_from_background($player, @will_destroy, @falling);
467
#- redraw neighbours because parts of neighbours have been erased by previous statement
468
put_image_to_background($_->{img}, $_->{'x'}, $_->{'y'})
469
foreach grep { !member($_, @will_destroy) && !member($_, @falling) } fastuniq(map { @{$_->{neighbours}} } @will_destroy, @falling);
473
$pdata{$player}{newroot}++;
474
if ($pdata{$player}{newroot} == $TIME_APPEARS_NEW_ROOT-1) {
475
$pdata{$player}{newroot_prelight} = 2;
476
$pdata{$player}{newroot_prelight_step} = 0;
478
if ($pdata{$player}{newroot} == $TIME_APPEARS_NEW_ROOT) {
479
$pdata{$player}{newroot_prelight} = 1;
480
$pdata{$player}{newroot_prelight_step} = 0;
482
if ($pdata{$player}{newroot} > $TIME_APPEARS_NEW_ROOT) {
484
$pdata{$player}{newroot_prelight} = 0;
485
play_sound(is_1p_game() ? 'newroot_solo' : 'newroot');
486
$pdata{$player}{newroot} = 0;
487
$pdata{$player}{oddswap} = !$pdata{$player}{oddswap};
488
remove_images_from_background($player, @{$sticked_bubbles{$player}});
489
foreach (@{$sticked_bubbles{$player}}) {
491
calc_real_pos($_, $player);
493
put_allimages_to_background($player);
495
$pdata{$player}{newrootlevel}++;
498
@{$root_bubbles{$player}} = ();
499
real_stick_bubble(create_bubble($player), $_, 0, $player, 0) foreach (0..(7-$pdata{$player}{oddswap}));
504
$background->blit($apprects{$player}, $app, $apprects{$player});
505
malus_change(@will_destroy + @falling - 2, $player);
509
sub print_next_bubble($$) {
510
my ($img, $player) = @_;
511
put_image_to_background($img, $next_bubble{$player}{'x'}, $next_bubble{$player}{'y'});
512
put_image_to_background($bubbles_anim{on_top_next}, $POS{$player}{left_limit}+$POS{next_bubble}{x}-3, $POS{next_bubble}{'y'}-2);
515
sub generate_new_bubble {
516
my ($player, $img) = @_;
517
$tobe_launched{$player} = $next_bubble{$player};
518
$tobe_launched{$player}{'x'} = ($POS{$player}{left_limit}+$POS{$player}{right_limit})/2 - $BUBBLE_SIZE/2;
519
$tobe_launched{$player}{'y'} = $POS{'initial_bubble_y'};
520
$next_bubble{$player} = $img ? create_bubble_given_img($img) : create_bubble($player);
521
$next_bubble{$player}{'x'} = $POS{$player}{left_limit}+$POS{next_bubble}{x}; #- necessary to keep coordinates, for verify_if_end
522
$next_bubble{$player}{'y'} = $POS{next_bubble}{'y'};
523
print_next_bubble($next_bubble{$player}{img}, $player);
527
#- ----------- game stuff -------------------------------------------------
529
sub handle_graphics($) {
534
foreach ($launched_bubble{$::p}, if_($fun ne 'erase_image', $tobe_launched{$::p})) {
535
$_ and $fun->($_->{img}, $_->{'x'}, $_->{'y'});
537
if ($fun == \&put_image && $pdata{$::p}{newroot_prelight}) {
538
if ($pdata{$::p}{newroot_prelight_step}++ > 30*$pdata{$::p}{newroot_prelight}) {
539
$pdata{$::p}{newroot_prelight_step} = 0;
541
if ($pdata{$::p}{newroot_prelight_step} <= 8) {
542
my $hurry_overwritten = 0;
543
foreach my $b (@{$sticked_bubbles{$::p}}) {
544
next if ($graphics_level == 1 && $b->{'cy'} > 0); #- in low graphics, only prelight first row
545
$b->{'cx'}+1 == $pdata{$::p}{newroot_prelight_step} and put_image($b->{img}, $b->{'x'}, $b->{'y'});
546
$b->{'cx'} == $pdata{$::p}{newroot_prelight_step} and put_image($bubbles_anim{white}, $b->{'x'}, $b->{'y'});
547
$b->{'cy'} > 6 and $hurry_overwritten = 1;
549
$hurry_overwritten && $pdata{$::p}{hurry_save_img} and print_hurry($::p, 1); #- hurry was potentially overwritten
552
if ($sticking_bubble{$::p} && $graphics_level > 1) {
553
my $b = $sticking_bubble{$::p};
554
if ($fun == \&erase_image) {
555
put_image($b->{img}, $b->{'x'}, $b->{'y'});
557
if ($pdata{$::p}{sticking_step} == listlength(@{$bubbles_anim{stick}})) {
558
$sticking_bubble{$::p} = undef;
560
put_image(${$bubbles_anim{stick}}[$pdata{$::p}{sticking_step}], $b->{'x'}, $b->{'y'});
561
if ($pdata{$::p}{sticking_step_slowdown}) {
562
$pdata{$::p}{sticking_step}++;
563
$pdata{$::p}{sticking_step_slowdown} = 0;
565
$pdata{$::p}{sticking_step_slowdown}++;
572
if ($graphics_level > 1) {
573
my $num = int($angle{$::p}*$CANON_ROTATIONS_NB/($PI/2) + 0.5)-$CANON_ROTATIONS_NB;
574
$fun->($canon{img}{$num},
575
($POS{$::p}{left_limit}+$POS{$::p}{right_limit})/2 - 50 + $canon{data}{$num}->[0],
576
$POS{'initial_bubble_y'} + 16 - 50 + $canon{data}{$num}->[1] ); #- 50/50 stand for half width/height of gfx/shoot/base.png
579
($POS{$::p}{left_limit}+$POS{$::p}{right_limit})/2 - 1 + 60*cos($angle{$::p}), #- 1 for $shooter->width/2
580
$POS{'initial_bubble_y'} + 16 - 1 - 60*sin($angle{$::p})); #- 1/1 stand for half width/height of gfx/shoot/shooter.png
583
if ($graphics_level == 3) {
584
$fun->($pinguin{$::p}{$pdata{$::p}{ping_right}{state}}[$pdata{$::p}{ping_right}{img}], $POS{$::p}{left_limit}+$POS{$::p}{pinguin}{x}, $POS{$::p}{pinguin}{'y'});
587
#- moving bubbles --> I want them on top of the rest
588
foreach (@{$malus_bubble{$::p}}, @{$falling_bubble{$::p}}, @{$exploding_bubble{$::p}}) {
589
$fun->($_->{img}, $_->{'x'}, $_->{'y'});
596
#- extract it from "handle_graphics" to optimize a bit animations
597
sub malus_change($$) {
598
my ($numb, $player) = @_;
599
return if $numb == 0 || is_1p_game();
601
$player = ($player eq 'p1') ? 'p2' : 'p1';
603
my $update_malus = sub($) {
605
my $malus = $pdata{$player}{malus};
610
$fun->($type, $POS{$player}{malus_x} - $type->width/2, $POS{'malus_y'} - $y_shift - $type->height);
611
$y_shift += $type->height - 1;
614
$print->($malus_gfx{tomate});
617
$print->($malus_gfx{banane});
622
$update_malus->(\&remove_image_from_background);
623
$pdata{$player}{malus} += $numb;
624
$update_malus->(\&put_image_to_background);
627
sub print_compressor() {
628
my $x = $POS{compressor_xpos};
629
my $y = $POS{top_limit} + $pdata{$PLAYERS[0]}{newrootlevel} * $ROW_SIZE;
630
my ($comp_main, $comp_ext) = ($imgbin{compressor_main}, $imgbin{compressor_ext});
632
my $drect = new SDL::Rect(-width => $comp_main->width, -height => $y,
633
-x => $x - $comp_main->width/2, '-y' => 0);
634
$background_orig->blit($drect, $background, $drect);
635
$display_on_app_disabled or $background_orig->blit($drect, $app, $drect);
636
push @update_rects, $drect;
638
put_image_to_background($comp_main, $x - $comp_main->width/2, $y - $comp_main->height);
640
$y -= $comp_main->height - 3;
643
put_image_to_background($comp_ext, $x - $comp_ext->width/2, $y - $comp_ext->height);
644
$y -= $comp_ext->height;
648
sub handle_game_events() {
650
if ($event->poll != 0) {
651
if ($event->type == SDL_KEYDOWN) {
652
my $keypressed = $event->key_sym();
655
my $pkey = is_1p_game() ? 'p2' : $::p;
656
foreach ('left', 'right', 'fire') {
657
$keypressed == $KEYS->{$pkey}{$_} and $actions{$::p}{$_} = 1, last;
661
if ($keypressed == SDLK_PAUSE) {
664
$mixer and $mixer->pause_music();
665
$imgbin{back_paused}->blit($apprects{main}, $app, $apprects{main});
668
my ($index, $side) = (0, 1);
669
while ($index || $side == 1) {
670
$pause = switch_image_on_background(${$imgbin{paused}}[$index], $POS{centerpanel}{x}, $POS{centerpanel}{'y'}, 1);
675
last pause_label if $event->poll != 0 && $event->type == SDL_KEYDOWN;
677
rand() < 0.2 and play_sound('snore');
678
switch_image_on_background($pause, $POS{centerpanel}{x}, $POS{centerpanel}{'y'});
680
if ($index == listlength(@{$imgbin{paused}})) {
686
switch_image_on_background($pause, $POS{centerpanel}{x}, $POS{centerpanel}{'y'});
687
iter_players { $actions{$::p}{left} = 0; $actions{$::p}{right} = 0; };
688
$mixer and $mixer->resume_music();
689
$background->blit($apprects{main}, $app, $apprects{main});
695
if ($event->type == SDL_KEYUP) {
696
my $keypressed = $event->key_sym();
699
my $pkey = is_1p_game() ? 'p2' : $::p;
700
foreach ('left', 'right', 'fire') {
701
$keypressed == $KEYS->{$pkey}{$_} and $actions{$::p}{$_} = 0, last;
706
if ($event->type == SDL_QUIT ||
707
$event->type == SDL_KEYDOWN && $event->key_sym() == SDLK_ESCAPE) {
713
sub print_scores($) {
714
my ($surface) = @_; #- TODO all this function has hardcoded coordinates
715
my $drect = new SDL::Rect(-width => 120, -height => 30, -x => 260, '-y' => 428);
716
$background_orig->blit($drect, $surface, $drect);
717
push @update_rects, $drect;
718
iter_players_ { #- sometimes called from within a iter_players so...
719
$surface->print($POS{$::p_}{scores_x}-SDL_TEXTWIDTH($pdata{$::p_}{score})/2, $POS{'scores_y'}, $pdata{$::p_}{score});
725
if (grep { $_->{cy} > 11 } @{$sticked_bubbles{$::p}}) {
726
$pdata{state} = "lost $::p";
728
$pdata{$::p}{ping_right}{state} = 'lose';
729
$pdata{$::p}{ping_right}{img} = 0;
731
my $won = $::p eq 'p1' ? 'p2' : 'p1';
732
$pdata{$won}{score}++;
733
$pdata{$won}{ping_right}{state} = 'win';
734
$pdata{$won}{ping_right}{img} = 0;
735
print_scores($background); print_scores($app);
737
$_ and ($_->{img} = $bubbles_anim{lose}, $_->{'x'}--, $_->{'y'}--) foreach ($launched_bubble{$::p}, $tobe_launched{$::p}, @{$malus_bubble{$::p}});
738
iter_players_ { remove_hurry($::p_); };
739
print_next_bubble($bubbles_anim{lose}, $::p);
741
@{$sticked_bubbles{$::p_}} = sort { $b->{'cx'}+$b->{'cy'}*10 <=> $a->{'cx'}+$a->{'cy'}*10 } @{$sticked_bubbles{$::p_}};
742
$sticking_bubble{$::p_} = undef;
743
$launched_bubble{$::p_} and destroy_bubbles($::p_, $launched_bubble{$::p_});
744
$launched_bubble{$::p_} = undef;
745
$pdata{$::p_}{newroot_prelight} = 0;
747
@{$malus_bubble{$::p}} = ();
751
if (is_1p_game() && listlength(@{$sticked_bubbles{$PLAYERS[0]}}) == 0) {
752
$pdata{state} = "won $PLAYERS[0]";
753
$pdata{$PLAYERS[0]}{ping_right}{state} = 'win';
754
$pdata{$PLAYERS[0]}{ping_right}{img} = 0;
755
$levels{current} and $levels{current}++;
756
$levels{$levels{current}} or $levels{current} = 0;
760
sub print_hurry($;$) {
761
my ($player, $dont_save_background) = @_;
762
my $t = switch_image_on_background($imgbin{hurry}{$player}, $POS{$player}{left_limit} + $POS{hurry}{x}, $POS{hurry}{'y'}, 1);
763
$dont_save_background or $pdata{$player}{hurry_save_img} = $t;
765
sub remove_hurry($) {
767
$pdata{$player}{hurry_save_img} and
768
switch_image_on_background($pdata{$player}{hurry_save_img}, $POS{$player}{left_limit} + $POS{hurry}{x}, $POS{hurry}{'y'});
769
$pdata{$player}{hurry_save_img} = undef;
773
#- ----------- mainloop helper --------------------------------------------
777
if ($pdata{state} eq 'game') {
778
handle_game_events();
780
$actions{$::p}{left} and $angle{$::p} += $LAUNCHER_SPEED;
781
$actions{$::p}{right} and $angle{$::p} -= $LAUNCHER_SPEED;
782
($angle{$::p} < 0.1) and $angle{$::p} = 0.1;
783
($angle{$::p} > $PI-0.1) and $angle{$::p} = $PI-0.1;
784
$pdata{$::p}{hurry}++;
785
if ($pdata{$::p}{hurry} > $TIME_HURRY_WARN) {
786
my $oddness = odd(int(($pdata{$::p}{hurry}-$TIME_HURRY_WARN)/(500/$TARGET_ANIM_SPEED))+1);
787
if ($pdata{$::p}{hurry_oddness} xor $oddness) {
795
$pdata{$::p}{hurry_oddness} = $oddness;
798
if (($actions{$::p}{fire} || $pdata{$::p}{hurry} == $TIME_HURRY_MAX) && !$launched_bubble{$::p}) {
799
play_sound('launch');
800
$launched_bubble{$::p} = $tobe_launched{$::p};
801
$launched_bubble{$::p}->{direction} = $angle{$::p};
802
$tobe_launched{$::p} = undef;
803
$actions{$::p}{fire} = 0;
804
$actions{$::p}{hadfire} = 1;
805
$pdata{$::p}{hurry} = 0;
809
if ($launched_bubble{$::p}) {
810
$launched_bubble{$::p}->{'x_old'} = $launched_bubble{$::p}->{'x'}; # save coordinates for potential collision
811
$launched_bubble{$::p}->{'y_old'} = $launched_bubble{$::p}->{'y'};
812
$launched_bubble{$::p}->{'x'} += $BUBBLE_SPEED * cos($launched_bubble{$::p}->{direction});
813
$launched_bubble{$::p}->{'y'} -= $BUBBLE_SPEED * sin($launched_bubble{$::p}->{direction});
814
if ($launched_bubble{$::p}->{x} < $POS{$::p}{left_limit}) {
815
play_sound('rebound');
816
$launched_bubble{$::p}->{x} = 2 * $POS{$::p}{left_limit} - $launched_bubble{$::p}->{x};
817
$launched_bubble{$::p}->{direction} -= 2*($launched_bubble{$::p}->{direction}-$PI/2);
819
if ($launched_bubble{$::p}->{x} > $POS{$::p}{right_limit} - $BUBBLE_SIZE) {
820
play_sound('rebound');
821
$launched_bubble{$::p}->{x} = 2 * ($POS{$::p}{right_limit} - $BUBBLE_SIZE) - $launched_bubble{$::p}->{x};
822
$launched_bubble{$::p}->{direction} += 2*($PI/2-$launched_bubble{$::p}->{direction});
824
if ($launched_bubble{$::p}->{'y'} <= $POS{top_limit} + $pdata{$::p}{newrootlevel} * $ROW_SIZE) {
825
my ($cx, $cy) = get_array_closest_pos($launched_bubble{$::p}->{x}, $launched_bubble{$::p}->{'y'}, $::p);
826
stick_bubble($launched_bubble{$::p}, $cx, $cy, $::p);
827
$launched_bubble{$::p} = undef;
829
foreach (@{$sticked_bubbles{$::p}}) {
830
if (is_collision($launched_bubble{$::p}, $_->{'x'}, $_->{'y'})) {
831
my ($cx, $cy) = get_array_closest_pos(($launched_bubble{$::p}->{'x_old'}+$launched_bubble{$::p}->{'x'})/2,
832
($launched_bubble{$::p}->{'y_old'}+$launched_bubble{$::p}->{'y'})/2,
834
stick_bubble($launched_bubble{$::p}, $cx, $cy, $::p);
835
$launched_bubble{$::p} = undef;
838
$pdata{$::p}{malus} > 0 and play_sound('malus');
839
while ($pdata{$::p}{malus} > 0 && listlength(@{$malus_bubble{$::p}}) < 7) {
840
my $b = create_bubble($::p);
842
$b->{'cx'} = int(rand(7));
843
} while (member($b->{'cx'}, map { $_->{'cx'} } @{$malus_bubble{$::p}}));
846
foreach (@{$sticked_bubbles{$::p}}) {
847
if ($_->{'cy'} > $b->{'stick_y'}) {
848
if ($_->{'cx'} == $b->{'cx'}
849
|| odd($_->{'cy'}+$pdata{$::p}{oddswap}) && ($_->{'cx'}+1) == $b->{'cx'}) {
850
$b->{'stick_y'} = $_->{'cy'};
855
calc_real_pos($b, $::p);
856
push @{$malus_bubble{$::p}}, $b;
857
malus_change(-1, $::p);
859
#- sort them and shift them
860
@{$malus_bubble{$::p}} = sort { $a->{'cx'} <=> $b->{'cx'} } @{$malus_bubble{$::p}};
862
$_->{'y'} += ($shifting+=7)+int(rand(20)) foreach @{$malus_bubble{$::p}};
870
!$tobe_launched{$::p} and generate_new_bubble($::p);
872
if (!$actions{$::p}{left} && !$actions{$::p}{right} && !$actions{$::p}{hadfire}) {
873
$pdata{$::p}{sleeping}++;
875
$pdata{$::p}{sleeping} = 0;
876
$pdata{$::p}{ping_right}{movelatency} = -20;
878
if ($pdata{$::p}{sleeping} > $TIMEOUT_PINGUIN_SLEEP) {
879
$pdata{$::p}{ping_right}{state} = 'sleep';
880
} elsif ($pdata{$::p}{ping_right}{state} eq 'sleep') {
881
$pdata{$::p}{ping_right}{state} = 'normal';
883
if ($pdata{$::p}{ping_right}{state} eq 'right' && !($actions{$::p}{right})
884
|| $pdata{$::p}{ping_right}{state} eq 'left' && !($actions{$::p}{left})
885
|| $pdata{$::p}{ping_right}{state} eq 'action' && ($pdata{$::p}{ping_right}{actionlatency}++ > 5)) {
886
$pdata{$::p}{ping_right}{state} = 'normal';
888
$actions{$::p}{right} and $pdata{$::p}{ping_right}{state} = 'right';
889
$actions{$::p}{left} and $pdata{$::p}{ping_right}{state} = 'left';
890
if ($actions{$::p}{hadfire}) {
891
$pdata{$::p}{ping_right}{state} = 'action';
892
$actions{$::p}{hadfire} = 0;
893
$pdata{$::p}{ping_right}{actionlatency} = 0;
895
if ($pdata{$::p}{ping_right}{state} eq 'normal' && ($pdata{$::p}{ping_right}{movelatency}++ > 10)) {
896
$pdata{$::p}{ping_right}{movelatency} = 0;
897
rand() < 0.4 and $pdata{$::p}{ping_right}{img} = int(rand(listlength(@{$pinguin{$::p}{normal}})));
900
if ($pdata{$::p}{ping_right}{img} >= listlength(@{$pinguin{$::p}{$pdata{$::p}{ping_right}{state}}})) {
901
$pdata{$::p}{ping_right}{img} = 0;
907
} elsif ($pdata{state} =~ /lost (.*)/) {
908
my $lost_slowdown if 0; #- ``if 0'' is Perl's way of doing what C calls ``static local variables''
909
if ($lost_slowdown++ > 1) {
913
if (listlength(@{$sticked_bubbles{$::p}})) {
914
my $b = shift @{$sticked_bubbles{$::p}};
915
put_image_to_background($bubbles_anim{lose}, --$b->{'x'}, --$b->{'y'});
916
# my $line = $b->{'cy'};
917
# while (listlength(@{$sticked_bubbles{$::p}}) && ${$sticked_bubbles{$::p}}[0]->{'cy'} == $line) {
918
# my $b = shift @{$sticked_bubbles{$::p}};
919
# put_image_to_background($bubbles_anim{lose}, --$b->{'x'}, --$b->{'y'});
922
if (listlength(@{$sticked_bubbles{$::p}}) == 0) {
923
$graphics_level == 1 and put_image($imgbin{win}{$::p eq 'p1' ? 'p2' : 'p1'}, $POS{centerpanel}{x}, $POS{centerpanel}{'y'});
925
put_image($imgbin{lose}, $POS{centerpanel}{'x'}, $POS{centerpanel}{'y'});
930
if (!listlength(@{$sticked_bubbles{$::p}})) {
931
$event->pump() while ($event->poll != 0);
935
die 'new_game' if $event->poll != 0 && $event->type == SDL_KEYDOWN;
938
if (listlength(@{$sticked_bubbles{$::p}}) && $graphics_level > 1) {
939
my $b = shift @{$sticked_bubbles{$::p}};
940
destroy_bubbles($::p, $b);
941
remove_image_from_background($b->{img}, $b->{'x'}, $b->{'y'});
942
#- be sure to redraw at least upper line
943
foreach (@{$b->{neighbours}}) {
944
next if !member($_, @{$sticked_bubbles{$::p}});
945
put_image_to_background($_->{img}, $_->{'x'}, $_->{'y'});
953
} elsif ($pdata{state} =~ /won (.*)/) {
954
put_image($imgbin{win}{$1}, $POS{centerpanel}{x}, $POS{centerpanel}{'y'});
955
if (listlength(@{$exploding_bubble{$1}}) == 0) {
957
die 'new_game' if $event->poll != 0 && $event->type == SDL_KEYDOWN;
961
die "oops unhandled game state ($pdata{state})\n";
965
#- things that need to be updated in all states of the game
968
foreach my $b (@{$malus_bubble{$::p}}) {
969
$b->{'y'} -= $MALUS_BUBBLE_SPEED;
970
if (get_array_yclosest($b->{'y'}) <= $b->{'stick_y'}) {
971
real_stick_bubble($b, $b->{'cx'}, $b->{'stick_y'}, $::p, 0);
972
push @$malus_end, $b;
975
listlength(@$malus_end) and @{$malus_bubble{$::p}} = difference2($malus_bubble{$::p}, $malus_end);
977
my $falling_end = [];
978
foreach my $b (@{$falling_bubble{$::p}}) {
979
if ($b->{wait_fall}) {
982
$b->{'y'} += $b->{speed};
983
$b->{speed} += $FREE_FALL_CONSTANT;
985
push @$falling_end, $b if $b->{'y'} > 470;
987
listlength(@$falling_end) and @{$falling_bubble{$::p}} = difference2($falling_bubble{$::p}, $falling_end);
989
my $exploding_end = [];
990
foreach my $b (@{$exploding_bubble{$::p}}) {
991
$b->{'x'} += $b->{speedx};
992
$b->{'y'} += $b->{speedy};
993
$b->{speedy} += $FREE_FALL_CONSTANT;
994
push @$exploding_end, $b if $b->{'y'} > 470;
996
if (listlength(@$exploding_end)) {
997
@{$exploding_bubble{$::p}} = difference2($exploding_bubble{$::p}, $exploding_end);
998
if ($pdata{state} =~ /lost (.*)/ && $::p ne $1 && listlength(@{$exploding_bubble{$::p}}) == 0 && !is_1p_game()) {
999
put_image($imgbin{win}{$::p}, $POS{centerpanel}{x}, $POS{centerpanel}{'y'});
1003
if (member($pdata{$::p}{ping_right}{state}, qw(win lose)) && ($pdata{$::p}{ping_right}{movelatency}++ > 5)) {
1004
my $state = $pdata{$::p}{ping_right}{state};
1005
$pdata{$::p}{ping_right}{movelatency} = 0;
1006
$pdata{$::p}{ping_right}{img}++;
1007
$pdata{$::p}{ping_right}{img} == listlength(@{$pinguin{$::p}{$state}})
1008
and $pdata{$::p}{ping_right}{img} = $pinguin{$::p}{"$state".'_roll_back_index'};
1014
#- ----------- init stuff -------------------------------------------------
1017
$app = new SDL::App(-flags => $sdl_flags | ($fullscreen ? SDL_FULLSCREEN : 0), -title => 'Frozen-Bubble', -width => 640, -height => 480);
1023
my $step if 0; $step ||= 0;
1024
put_image($imgbin{loading_step}, 100 + $step*12, 10);
1030
-r "$FPATH/$_" or die "[*ERROR*] the datafiles seem to be missing! (could not read `$FPATH/$_')\n".
1031
" The datafiles need to go to `$FPATH'.\n"
1032
foreach qw(gfx snd data);
1034
print '[SDL Init] ';
1036
$font = new SDL::Font("$FPATH/gfx/font.png");
1037
$apprects{main} = new SDL::Rect(-width => $app->width, -height => $app->height);
1038
$event = new SDL::Event;
1039
SDL::Cursor::show(0);
1040
$total_time = $app->ticks();
1041
$imgbin{loading} = add_image('loading.png');
1042
put_image($imgbin{loading}, 10, 10); $app->flip();
1043
$imgbin{loading_step} = add_image('loading_step.png');
1045
print_step('[Graphics');
1046
$imgbin{back_2p} = new SDL::Surface(-name => "$FPATH/gfx/backgrnd.png");
1047
$imgbin{back_1p} = new SDL::Surface(-name => "$FPATH/gfx/back_one_player.png");
1048
$background = new SDL::Surface(-width => $app->width, -height => $app->height, -depth => 32, -Amask => '0 but true');
1049
$background_orig = new SDL::Surface(-width => $app->width, -height => $app->height, -depth => 32, -Amask => '0 but true');
1050
$imgbin{backstartfull} = new SDL::Surface(-name => "$FPATH/gfx/menu/back_start.png");
1053
add_bubble_image("balls/bubble-$_.gif") foreach (1..8);
1054
$bubbles_anim{white} = add_image("balls/bubble_prelight.png");
1055
$bubbles_anim{lose} = add_image("balls/bubble_lose.png");
1056
$bubbles_anim{on_top_next} = add_image("on_top_next.png");
1057
push @{$bubbles_anim{stick}}, add_image("balls/stick_effect_$_.png") foreach (0..7);
1059
$shooter = add_image("shoot/shooter.png");
1060
$canon{img}{$_} = add_image("shoot/base_$_.png") foreach (-$CANON_ROTATIONS_NB..$CANON_ROTATIONS_NB);
1061
/(\S+) (\S+) (\S+)/ and $canon{data}{$1} = [ $2, $3 ] foreach cat_("$FPATH/gfx/shoot/data"); #- quantity of shifting needed (because of crop reduction)
1062
$malus_gfx{banane} = add_image('banane.png');
1063
$malus_gfx{tomate} = add_image('tomate.png');
1066
push @{$imgbin{paused}}, add_image("pause_$_.png") foreach (2..5);
1067
$imgbin{back_paused} = add_image('back_paused.png');
1068
$imgbin{lose} = add_image("lose_panel.png");
1070
$imgbin{compressor_main} = add_image('compressor_main.png');
1071
$imgbin{compressor_ext} = add_image('compressor_ext.png');
1073
$imgbin{txt_1pgame_off} = add_image('menu/txt_1pgame_off.png');
1074
$imgbin{txt_1pgame_over} = add_image('menu/txt_1pgame_over.png');
1075
$imgbin{txt_2pgame_off} = add_image('menu/txt_2pgame_off.png');
1076
$imgbin{txt_2pgame_over} = add_image('menu/txt_2pgame_over.png');
1077
$imgbin{txt_fullscreen_off} = add_image('menu/txt_fullscreen_off.png');
1078
$imgbin{txt_fullscreen_over} = add_image('menu/txt_fullscreen_over.png');
1079
$imgbin{txt_fullscreen_act_off} = add_image('menu/txt_fullscreen_act_off.png');
1080
$imgbin{txt_fullscreen_act_over} = add_image('menu/txt_fullscreen_act_over.png');
1081
$imgbin{txt_keys_off} = add_image('menu/txt_keys_off.png');
1082
$imgbin{txt_keys_over} = add_image('menu/txt_keys_over.png');
1083
$imgbin{txt_sound_off} = add_image('menu/txt_sound_off.png');
1084
$imgbin{txt_sound_over} = add_image('menu/txt_sound_over.png');
1085
$imgbin{txt_sound_act_off} = add_image('menu/txt_sound_act_off.png');
1086
$imgbin{txt_sound_act_over} = add_image('menu/txt_sound_act_over.png');
1087
$imgbin{txt_graphics_1_off} = add_image('menu/txt_graphics_1_off.png');
1088
$imgbin{txt_graphics_1_over} = add_image('menu/txt_graphics_1_over.png');
1089
$imgbin{txt_graphics_2_off} = add_image('menu/txt_graphics_2_off.png');
1090
$imgbin{txt_graphics_2_over} = add_image('menu/txt_graphics_2_over.png');
1091
$imgbin{txt_graphics_3_off} = add_image('menu/txt_graphics_3_off.png');
1092
$imgbin{txt_graphics_3_over} = add_image('menu/txt_graphics_3_over.png');
1093
$imgbin{txt_highscores_off} = add_image('menu/txt_highscores_off.png');
1094
$imgbin{txt_highscores_over} = add_image('menu/txt_highscores_over.png');
1095
$imgbin{void_panel} = add_image('menu/void_panel.png');
1097
$imgbin{back_hiscores} = add_image('back_hiscores.png');
1098
$imgbin{hiscore_frame} = add_image('hiscore_frame.png');
1100
$imgbin{banner_artwork} = add_image('menu/banner_artwork.png');
1101
$imgbin{banner_soundtrack} = add_image('menu/banner_soundtrack.png');
1102
$imgbin{banner_cpucontrol} = add_image('menu/banner_cpucontrol.png');
1105
$imgbin{frozen} = add_image('intro/txt_frozen.png');
1106
$imgbin{bubble} = add_image('intro/txt_bubble.png');
1107
$imgbin{intro_penguin_imgs}->{$_} = add_image("intro/intro_$_.png") foreach (1..19);
1110
$imgbin{hurry}{$::p} = add_image("hurry_$::p.png");
1111
$pinguin{$::p}{normal} = [ map { add_image($_) } ("pinguins/base_$::p.png", map { "pinguins/base_$::p"."_extra_0$_.png" } (1..3)) ];
1112
$pinguin{$::p}{sleep} = [ add_image("pinguins/sleep_$::p.png") ];
1113
$pinguin{$::p}{left} = [ add_image("pinguins/move_left_$::p.png") ];
1114
$pinguin{$::p}{right} = [ add_image("pinguins/move_right_$::p.png") ];
1115
$pinguin{$::p}{action} = [ add_image("pinguins/action_$::p.png") ];
1116
$pinguin{$::p}{win} = [ map { add_image("pinguins/$::p"."_win_$_.png") } qw(1 2 3 4 5 6 7 8 6) ];
1117
$pinguin{$::p}{win_roll_back_index} = 4;
1118
$pinguin{$::p}{lose} = [ map { add_image("pinguins/$::p"."_loose_$_.png") } qw(1 2 3 4 5 6 7 8 9) ];
1119
$pinguin{$::p}{lose_roll_back_index} = 5;
1120
$pinguin{$::p}{win} = [ map { add_image("pinguins/$::p"."_win_$_.png") } qw(1 2 3 4 5 6 7 8 6) ];
1121
$pinguin{$::p}{walkright} = [ map { add_image("pinguins/$::p"."_dg_walk_0$_.png") } qw(1 2 3 4 5 6) ];
1122
$imgbin{win}{$::p} = add_image("win_panel_$::p.png");
1123
$pdata{$::p}{score} = 0;
1129
foreach my $line (cat_("$FPATH/data/levels")) {
1130
if ($line !~ /\S/) {
1137
foreach (split ' ', $line) {
1138
/-/ or push @{$levels{$lev_number}}, { cx => $col_numb, cy => $row_numb, img_num => $_ };
1144
print_step("[$lev_number levels] ");
1146
if ($mixer eq 'SOUND_DISABLED') {
1150
play_music('intro');
1151
$mixer->pause_music();
1155
fb_c_stuff::init_effects($FPATH);
1162
listlength(@{$levels{$level}}) or die "No such level or void level ($level).\n";
1163
foreach my $l (@{$levels{$level}}) {
1165
my $img = $l->{img_num} =~ /^\d+$/ ? $bubbles_images[$l->{img_num}] : $bubbles_anim{lose};
1166
real_stick_bubble(create_bubble_given_img($img), $l->{cx}, $l->{cy}, $::p, 0);
1175
if ($event->type == SDL_KEYDOWN) {
1176
$keyp = $event->key_sym();
1178
} while ($event->type != SDL_KEYDOWN);
1179
do { $event->wait() } while ($event->type != SDL_KEYUP);
1183
sub display_highscores() {
1185
$imgbin{back_hiscores}->blit($apprects{main}, $app, $apprects{main});
1187
$display_on_app_disabled = 1;
1190
$POS{top_limit} = $POS{init_top_limit};
1192
my ($high_posx, $high_posy) = (85, 80);
1193
my $high_rect = new SDL::Rect('-x' => $POS{p1}{left_limit} & 0xFFFFFFFC, '-y' => $POS{top_limit} & 0xFFFFFFFC,
1194
'-width' => ($POS{p1}{right_limit}-$POS{p1}{left_limit}) & 0xFFFFFFFC, -height => ($POS{'initial_bubble_y'}-$POS{top_limit}-10) & 0xFFFFFFFC);
1196
my $centered_print = sub($$$) {
1197
my ($x, $y, $txt) = @_;
1198
$app->print($x+($imgbin{hiscore_frame}->width-SDL_TEXTWIDTH(uc($txt)))/2 - 6,
1199
$y+$imgbin{hiscore_frame}->height - 8, uc($txt));
1202
foreach my $high (ordered_highscores()) {
1204
@{$sticked_bubbles{$::p}} = ();
1205
@{$root_bubbles{$::p}} = ();
1206
$pdata{$::p}{newrootlevel} = 0;
1207
$pdata{$::p}{oddswap} = 0;
1209
$imgbin{back_1p}->blit($high_rect, $background, $high_rect);
1210
open_level($high->{level});
1211
put_image($imgbin{hiscore_frame}, $high_posx - 7, $high_posy - 6);
1212
fb_c_stuff::shrink($app->{-surface}, my_display_format($background)->{-surface}, $high_posx, $high_posy, $high_rect->{-rect}, 4);
1213
$centered_print->($high_posx, $high_posy, $high->{name});
1214
$centered_print->($high_posx, $high_posy+20, "LVL-".$high->{level});
1215
my $min = int($high->{time}/60);
1216
my $sec = int($high->{time} - $min*60); length($sec) == 1 and $sec = "0$sec";
1217
$centered_print->($high_posx, $high_posy+40, "$min'$sec''");
1219
$high_posx > 550 and $high_posx = 85, $high_posy += 190;
1220
$high_posy > 440 and last;
1223
$display_on_app_disabled = 0;
1225
$event->pump() while ($event->poll != 0);
1229
sub keysym_to_char($) { my ($key) = @_; eval("$key eq SDLK_$_") and return uc($_) foreach @fbsyms::syms }
1233
# $w->{intro} = [ 'text_intro_line1', 'text_intro_line2', ... ]
1234
# $w->{entries} = [ { q => 'question1?', a => \$var_answer1, f => 'flags' }, {...} ] flags: ONE_CHAR
1235
# $w->{outro} = 'text_outro_uniline'
1236
# $w->{erase_background} = $background_right_one
1238
my $xpos_panel = (640-$imgbin{void_panel}->width)/2;
1239
my $ypos_panel = (480-$imgbin{void_panel}->height)/2;
1240
put_image($imgbin{void_panel}, $xpos_panel, $ypos_panel);
1243
my $ypos = $ypos_panel + 5;
1245
foreach my $i (@{$w->{intro}}) {
1246
my $xpos = (640-SDL_TEXTWIDTH($i))/2;
1247
$app->print($xpos, $ypos, $i);
1255
foreach my $entry (@{$w->{entries}}) {
1256
$xpos = (640-$imgbin{void_panel}->width)/2 + 100 - SDL_TEXTWIDTH($entry->{'q'})/2;
1257
$app->print($xpos, $ypos, $entry->{'q'});
1259
my $srect_mulchar_redraw = new SDL::Rect(-width => $imgbin{void_panel}->width, -height => 30,
1260
-x => $xpos + 140 - $xpos_panel, '-y' => $ypos - $ypos_panel);
1261
my $drect_mulchar_redraw = new SDL::Rect(-width => $imgbin{void_panel}->width, -height => 30,
1262
-x => $xpos + 140, '-y' => $ypos);
1266
$k == SDLK_ESCAPE and $ok = 0, last entries;
1267
play_sound('typewriter');
1268
if ($entry->{f} =~ 'ONE_CHAR' || $k != SDLK_RETURN) {
1269
my $x_echo = (640-$imgbin{void_panel}->width)/2 + 200;
1270
if ($entry->{f} =~ 'ONE_CHAR') {
1272
$app->print($x_echo, $ypos, keysym_to_char($k));
1274
$k = keysym_to_char($k);
1275
length($k) == 1 && length($txt) < 8 and $txt .= $k;
1276
member($k, qw(BACKSPACE DELETE LEFT)) and $txt =~ s/.$//;
1277
$imgbin{void_panel}->blit($srect_mulchar_redraw, $app, $drect_mulchar_redraw);
1278
$app->print($x_echo, $ypos, $txt);
1282
$entry->{f} =~ 'ONE_CHAR' || $k == SDLK_RETURN and last;
1284
$entry->{answer} = $txt;
1289
${$_->{a}} = $_->{answer} foreach @{$w->{entries}};
1290
$xpos = (640-SDL_TEXTWIDTH($w->{outro}))/2;
1291
$ypos = (480+$imgbin{void_panel}->height)/2 - 33;
1292
$app->print($xpos, $ypos, $w->{outro});
1294
play_sound('menu_selected');
1297
play_sound('cancel');
1300
erase_image_from($imgbin{void_panel}, $xpos_panel, $ypos_panel, $w->{erase_background});
1302
$event->pump() while ($event->poll != 0);
1307
$display_on_app_disabled = 1;
1311
$backgr = $imgbin{back_2p};
1313
$TIME_APPEARS_NEW_ROOT = 11;
1314
$TIME_HURRY_WARN = 250;
1315
$TIME_HURRY_MAX = 375;
1316
} elsif (is_1p_game()) {
1317
$backgr = $imgbin{back_1p};
1319
$TIME_APPEARS_NEW_ROOT = 8;
1320
$TIME_HURRY_WARN = 400;
1321
$TIME_HURRY_MAX = 525;
1322
$POS{top_limit} = $POS{init_top_limit};
1323
$pdata{$PLAYERS[0]}{score} = $levels{current} || "RANDOM";
1328
$backgr->blit($apprects{main}, $background_orig, $apprects{main});
1329
$background_orig->blit($apprects{main}, $background, $apprects{main});
1332
$actions{$::p}{left} = 0;
1333
$actions{$::p}{right} = 0;
1334
$actions{$::p}{fire} = 0;
1335
$angle{$::p} = $PI/2;
1336
@{$sticked_bubbles{$::p}} = ();
1337
@{$malus_bubble{$::p}} = ();
1338
@{$root_bubbles{$::p}} = ();
1339
$launched_bubble{$::p} = undef;
1340
$sticking_bubble{$::p} = undef;
1341
$pdata{$::p}{newroot} = 0;
1342
$pdata{$::p}{newroot_prelight} = 0;
1343
$pdata{$::p}{oddswap} = 0;
1344
$pdata{$::p}{ping_right}{state} = 'normal';
1345
$pdata{$::p}{ping_right}{img} = 0;
1346
$pdata{$::p}{malus} = 0;
1347
$pdata{$::p}{hurry} = 0;
1348
$pdata{$::p}{newrootlevel} = 0;
1349
$apprects{$::p} = new SDL::Rect('-x' => $POS{$::p}{left_limit}, '-y' => $POS{top_limit},
1350
-width => $POS{$::p}{right_limit}-$POS{$::p}{left_limit}, -height => $POS{'initial_bubble_y'}-$POS{top_limit});
1352
print_scores($background);
1354
is_1p_game() and print_compressor();
1356
if ($levels{current}) {
1357
open_level($levels{current});
1359
foreach my $cy (0..4) {
1360
foreach my $cx (0..(6+odd($cy+1))) {
1361
my $b = create_bubble();
1362
real_stick_bubble($b, $cx, $cy, $PLAYERS[0], 0); #- this doesn't map well to the 'iter_players' subroutine..
1363
is_2p_game() and real_stick_bubble(create_bubble_given_img($b->{img}), $cx, $cy, $PLAYERS[1], 0);
1368
$next_bubble{$PLAYERS[0]} = create_bubble($PLAYERS[0]);
1369
generate_new_bubble($PLAYERS[0]);
1371
$next_bubble{$PLAYERS[1]} = create_bubble_given_img($tobe_launched{$PLAYERS[0]}->{img});
1372
generate_new_bubble($PLAYERS[1], $next_bubble{$PLAYERS[0]}->{img});
1375
if ($graphics_level == 1) {
1376
$background->blit($apprects{main}, $app, $apprects{main});
1379
fb_c_stuff::effect($app->{-surface}, my_display_format($background)->{-surface});
1382
$display_on_app_disabled = 0;
1384
$event->pump() while ($event->poll != 0);
1385
$pdata{state} = 'game';
1388
sub ordered_highscores() { return sort { $b->{level} <=> $a->{level} || $a->{time} <=> $b->{time} } @$HISCORES }
1390
sub handle_new_hiscores() {
1391
is_1p_game() or return;
1393
my @ordered = ordered_highscores();
1394
my $worst = pop @ordered;
1396
my $total_seconds = ($app->ticks() - $time_1pgame)/1000;
1398
if (listlength(@$HISCORES) == 10
1399
&& ($levels{current} < $worst->{level}
1400
|| $levels{current} == $worst->{level} && $total_seconds > $worst->{time})) {
1404
play_sound('applause');
1407
$new_entry{level} = $levels{current};
1408
$new_entry{time} = $total_seconds;
1409
ask_from({ intro => [ 'CONGRATULATIONS !', "YOU HAVE A HIGHSCORE !", '' ],
1410
entries => [ { 'q' => 'YOUR NAME ?', 'a' => \$new_entry{name} } ],
1411
outro => 'GREAT GAME !',
1412
erase_background => $background,
1415
return if $new_entry{name} eq '';
1417
push @$HISCORES, \%new_entry;
1418
if (listlength(@$HISCORES) == 11) {
1419
my @high = ordered_highscores();
1424
output($hiscorefile, Data::Dumper->Dump([$HISCORES], [qw(HISCORES)]));
1425
display_highscores();
1429
#- ----------- mainloop ---------------------------------------------------
1432
my $synchro_ticks = $app->ticks();
1434
handle_graphics(\&erase_image);
1436
handle_graphics(\&put_image);
1438
$app->update(@update_rects);
1441
my $to_wait = $TARGET_ANIM_SPEED - ($app->ticks() - $synchro_ticks);
1442
$to_wait > 0 and fb_c_stuff::fbdelay($to_wait);
1446
#- ----------- intro stuff ------------------------------------------------
1452
start => { type => 'time', value => 0 },
1454
animations => [ qw(1 2 3 4 5 6 7 6 5 4 3 2) ],
1456
music => { start => { type => 'time', value => 1 } },
1457
bubble_fall1 => { start => { type => 'synchro', value => 0x01 },
1458
type => 'bubble_falling', img => 2, xpos => 200, xaccel => -1.5 },
1459
bubble_fall2 => { start => { type => 'synchro', value => 0x02 },
1460
type => 'bubble_falling', img => 3, xpos => 350, xaccel => 1 },
1461
bubble_fall3 => { start => { type => 'synchro', value => 0x03 },
1462
type => 'bubble_falling', img => 4, xpos => 400, xaccel => 2 },
1464
start => { type => 'synchro', value => 0x21 },
1466
animations => [ qw(8 9 10 11 12 11 10 9) ],
1469
start => { type => 'synchro', value => 0x22 },
1471
animations => [ qw(12 13 14 15 14 13) ],
1474
start => { type => 'synchro', value => 0x31 },
1476
animations => [ qw(15 16 17 18 19 18 17 16) ],
1478
txt_frozen_arriving => {
1479
start => { type => 'synchro', value => 0x31 },
1480
type => 'bitmap_animation',
1481
img => $imgbin{frozen},
1482
finalpos => { x => 300, 'y' => 100 },
1485
txt_bubble_arriving => {
1486
start => { type => 'synchro', value => 0x32 },
1487
type => 'bitmap_animation',
1488
img => $imgbin{bubble},
1489
finalpos => { x => 340, 'y' => 155 },
1495
animation_speed => 20
1500
my ($slowdown_number, $slowdown_frame);
1502
if ($mixer && 0) { #- temporarily desactivate the intro storyboard because it's not finised yet
1503
my $back_start = new SDL::Surface(-name => "$FPATH/intro/back_intro.png");
1504
$back_start->blit($apprects{main}, $app, $apprects{main});
1508
my @bubbles_falling;
1509
my @bitmap_animations;
1512
my $start_time = $app->ticks;
1513
my $current_time = $start_time;
1515
while (!$start_menu) {
1516
my $synchro_ticks = $app->ticks();
1518
my $current_time_ = int(($app->ticks - $start_time)/1000);
1519
my $anim_step_ = fb_c_stuff::get_synchro_value();
1521
if ($anim_step_ != $anim_step || $current_time_ != $current_time) {
1522
$anim_step = $anim_step_;
1523
$current_time = $current_time_;
1524
printf "Anim step: %12s Time: <$current_time>\n", sprintf "<0x%02x>", $anim_step;
1526
foreach my $evt (keys %storyboard) {
1527
next if $storyboard{$evt}->{already};
1528
if ($storyboard{$evt}->{start}->{type} eq 'time' && $storyboard{$evt}->{start}->{value} <= $current_time
1529
|| $storyboard{$evt}->{start}->{type} eq 'synchro' && $storyboard{$evt}->{start}->{value} eq $anim_step) {
1530
$storyboard{$evt}->{already} = 1;
1531
print "*** Starting <$evt>\n";
1532
$evt eq 'music' and $mixer->resume_music();
1533
if ($storyboard{$evt}->{type} eq 'penguin') {
1534
$penguin = { animations => $storyboard{$evt}->{animations},
1536
anim_step => $sb_params{animation_speed} };
1538
if ($storyboard{$evt}->{type} eq 'bubble_falling') {
1539
push @bubbles_falling, { img => $bubbles_images[$storyboard{$evt}->{img}], 'y' => 0, speed => 3,
1540
x => $storyboard{$evt}->{xpos}, xaccel => $storyboard{$evt}->{xaccel} };
1542
if ($storyboard{$evt}->{type} eq 'bitmap_animation') {
1543
push @bitmap_animations, { img => $storyboard{$evt}->{img}, 'y' => 0,
1544
x => $storyboard{$evt}->{finalpos}->{x},
1545
finaly => $storyboard{$evt}->{finalpos}->{'y'},
1546
factor => $storyboard{$evt}->{factor},
1552
$anim_step == 0x09 and $start_menu = 1;
1556
$penguin->{anim_step}++;
1557
if ($penguin->{anim_step} >= $sb_params{animation_speed}) {
1558
my $img_number = ${$penguin->{animations}}[$penguin->{current_anim}];
1559
erase_image_from($imgbin{intro_penguin_imgs}->{$img_number}, 260, 293, $back_start);
1560
$penguin->{anim_step} = 0;
1561
$penguin->{current_anim}++;
1562
$penguin->{current_anim} == listlength(@{$penguin->{animations}}) and $penguin->{current_anim} = 0;
1563
$img_number = ${$penguin->{animations}}[$penguin->{current_anim}];
1564
put_image($imgbin{intro_penguin_imgs}->{$img_number}, 260, 293);
1568
foreach my $b (@bubbles_falling) {
1569
erase_image_from($b->{img}, $b->{x}, $b->{'y'}, $back_start);
1570
$b->{'x'} += $b->{xaccel};
1571
$b->{'y'} += $b->{speed};
1572
if ($b->{'y'} >= 360 && !$b->{already_rebound}) {
1573
$b->{already_rebound} = 1;
1574
$b->{'y'} = 2*360 - $b->{'y'};
1575
$b->{speed} *= -0.5;
1577
$b->{speed} += $FREE_FALL_CONSTANT;
1578
$b->{kill} = $b->{'y'} > 470;
1579
$b->{kill} or put_image($b->{img}, $b->{x}, $b->{'y'});
1581
@bubbles_falling = grep { !$_->{kill} } @bubbles_falling;
1583
erase_image_from($_->{img}, $_->{x}, $_->{'y'}, $back_start) foreach @bitmap_animations;
1584
foreach my $b (@bitmap_animations) {
1585
foreach (0..$slowdown_frame) {
1586
$b->{'y'} = $b->{'finaly'} - 200*cos(3*$b->{step})/exp($b->{step}*$b->{step});
1587
$b->{step} += 0.015 * $b->{factor};
1590
$slowdown_frame = 0;
1591
put_image($_->{img}, $_->{x}, $_->{'y'}) foreach @bitmap_animations;
1593
$app->update(@update_rects);
1596
my $to_wait = $TARGET_ANIM_SPEED - ($app->ticks() - $synchro_ticks);
1598
$app->delay($to_wait);
1600
# print "slow by: <$to_wait>\n";
1601
$slowdown_number += -$to_wait;
1602
if ($slowdown_number > $TARGET_ANIM_SPEED) {
1603
$slowdown_frame = int($slowdown_number / $TARGET_ANIM_SPEED);
1604
$slowdown_number -= $slowdown_frame * $TARGET_ANIM_SPEED;
1605
# print "skip frames: <$slowdown_frame>\n";
1610
$event->poll != 0 && $event->type == SDL_KEYDOWN && member($event->key_sym(), (SDLK_RETURN, SDLK_SPACE, SDLK_KP_ENTER, SDLK_ESCAPE))
1611
and $start_menu = 2;
1617
# if ($start_menu == 1) {
1618
# my $bkg = new SDL::Surface(-width => $app->width, -height => $app->height, -depth => 32, -Amask => '0 but true');
1619
# $app->blit($apprects{main}, $bkg, $apprects{main});
1627
#- ----------- menu stuff -------------------------------------------------
1630
my ($from_intro, $back_from_intro) = @_;
1632
handle_new_hiscores();
1634
$mixer and $mixer->music_paused() and $mixer->resume_music();
1636
play_music('intro', 8);
1640
if (!$from_intro || !$back_from_intro) {
1641
$back_start = $imgbin{backstartfull};
1642
$back_start->blit($apprects{main}, $app, $apprects{main});
1644
$back_start = $back_from_intro;
1648
my $rest_app = sub { restart_app(); $back_start->blit($apprects{main}, $app, $apprects{main}); $app->flip(); $invalidate_all->(); 1; };
1650
my $menu_start_sound = sub {
1651
if (!init_sound()) {
1654
play_music('intro', 8);
1659
my $menu_stop_sound = sub {
1660
if ($mixer && $mixer->playing_music()) {
1661
$app->delay(10) while $mixer->fading_music(); #- mikmod will deadlock if we try to fade_out while still fading in
1662
$mixer->playing_music() and $mixer->fade_out_music(500); $app->delay(450);
1663
$app->delay(10) while $mixer->playing_music(); #- mikmod will segfault if we try to load a music while old one is still fading out
1669
my $menu_display_highscores = sub {
1670
display_highscores();
1672
$back_start->blit($apprects{main}, $app, $apprects{main});
1674
$invalidate_all->();
1677
my $change_keys = sub {
1678
ask_from({ intro => [ 'PLEASE ENTER NEW KEYS' ],
1680
{ 'q' => 'RIGHT-PL/LEFT?', 'a' => \$KEYS->{p2}{left}, f => 'ONE_CHAR' },
1681
{ 'q' => 'RIGHT-PL/RIGHT?', 'a' => \$KEYS->{p2}{right}, f => 'ONE_CHAR' },
1682
{ 'q' => 'RIGHT-PL/FIRE?', 'a' => \$KEYS->{p2}{fire}, f => 'ONE_CHAR' },
1683
{ 'q' => 'LEFT-PL/LEFT?', 'a' => \$KEYS->{p1}{left}, f => 'ONE_CHAR' },
1684
{ 'q' => 'LEFT-PL/RIGHT?', 'a' => \$KEYS->{p1}{right}, f => 'ONE_CHAR' },
1685
{ 'q' => 'LEFT-PL/FIRE?', 'a' => \$KEYS->{p1}{fire}, f => 'ONE_CHAR' },
1687
outro => 'THANKS !',
1688
erase_background => $back_start
1690
$invalidate_all->();
1693
my ($MENU_FIRSTY, $SPACING, $CATEGORIES_SPACING) = (50, 52, 15);
1694
my %menupos = ( '1pgame' => { 'x' => 58, 'y' => $MENU_FIRSTY },
1695
'2pgame' => { 'x' => 58, 'y' => $MENU_FIRSTY + $SPACING },
1696
'fullscreen' => { 'x' => 58, 'y' => $MENU_FIRSTY + 2 * $SPACING + $CATEGORIES_SPACING },
1697
'graphics' => { 'x' => 58, 'y' => $MENU_FIRSTY + 3 * $SPACING + $CATEGORIES_SPACING },
1698
'sound' => { 'x' => 58, 'y' => $MENU_FIRSTY + 4 * $SPACING + $CATEGORIES_SPACING },
1699
'keys' => { 'x' => 58, 'y' => $MENU_FIRSTY + 5 * $SPACING + $CATEGORIES_SPACING },
1700
'highscores' => { 'x' => 58, 'y' => $MENU_FIRSTY + 6 * $SPACING + $CATEGORIES_SPACING },
1702
my %menu_entries = ( '1pgame' => { pos => 1, type => 'rungame', run => sub { @PLAYERS = ('p1'); $levels{current} = 1; $time_1pgame = $app->ticks() } },
1703
'2pgame' => { pos => 2, type => 'rungame', run => sub { @PLAYERS = qw(p1 p2); $levels{current} = undef; } },
1704
'fullscreen' => { pos => 3, type => 'toggle', act => sub { $fullscreen = 1; $rest_app->() }, unact => sub { $fullscreen = 0; $rest_app->() }, value => $fullscreen },
1705
'graphics' => { pos => 4, type => 'range', valuemin => 1, valuemax => 3, change => sub { $graphics_level = $_[0] }, value => $graphics_level },
1706
'sound' => { pos => 5, type => 'toggle', act => sub { $menu_start_sound->() }, unact => sub { $menu_stop_sound->() }, value => $mixer },
1707
'keys' => { pos => 6, type => 'run', run => sub { $change_keys->() } },
1708
'highscores' => { pos => 7, type => 'run', run => sub { $menu_display_highscores->() } },
1710
my $current_pos if 0; $current_pos ||= 1;
1712
$invalidate_all = sub { push @menu_invalids, $menu_entries{$_}->{pos} foreach keys %menu_entries };
1714
my $menu_update = sub {
1716
foreach my $m (keys %menu_entries) {
1717
member($menu_entries{$m}->{pos}, @menu_invalids) or next;
1719
$menu_entries{$m}->{type} eq 'toggle' && $menu_entries{$m}->{value} and $txt .= "_act";
1720
$menu_entries{$m}->{type} eq 'range' and $txt .= "_$menu_entries{$m}->{value}";
1721
$txt .= $menu_entries{$m}->{pos} == $current_pos ? '_over' : '_off';
1722
erase_image_from($imgbin{$txt}, $menupos{$m}{'x'}, $menupos{$m}{'y'}, $back_start);
1723
put_image($imgbin{$txt}, $menupos{$m}{'x'}, $menupos{$m}{'y'});
1725
@menu_invalids = ();
1726
$app->update(@update_rects);
1730
$invalidate_all->();
1732
$event->pump() while ($event->poll != 0);
1735
my ($BANNER_START, $BANNER_SPACING) = (720, 80);
1736
my %banners = (artwork => $BANNER_START,
1737
soundtrack => $BANNER_START + $imgbin{banner_artwork}->width + $BANNER_SPACING,
1738
cpucontrol => $BANNER_START + $imgbin{banner_artwork}->width + $BANNER_SPACING + $imgbin{banner_soundtrack}->width + $BANNER_SPACING);
1739
my ($BANNER_MINX, $BANNER_MAXX, $BANNER_Y) = (39, 296, 445);
1740
my $banners_max = $banners{cpucontrol} - (640 - ($BANNER_MAXX - $BANNER_MINX)) + $BANNER_SPACING;
1741
my $banner_rect = new SDL::Rect(-width => $BANNER_MAXX-$BANNER_MINX, -height => 30, '-x' => $BANNER_MINX, '-y' => $BANNER_Y);
1743
while (!$start_game) {
1744
my $synchro_ticks = $app->ticks();
1746
$graphics_level > 1 and $back_start->blit($banner_rect, $app, $banner_rect);
1749
if ($event->poll != 0) {
1750
if ($event->type == SDL_KEYDOWN) {
1751
my $keypressed = $event->key_sym();
1752
if (member($keypressed, (SDLK_DOWN, SDLK_RIGHT)) && $current_pos < max(map { $menu_entries{$_}->{pos} } keys %menu_entries)) {
1754
push @menu_invalids, $current_pos-1, $current_pos;
1755
play_sound('menu_change');
1757
if (member($keypressed, (SDLK_UP, SDLK_LEFT)) && $current_pos > 1) {
1759
push @menu_invalids, $current_pos, $current_pos+1;
1760
play_sound('menu_change');
1763
if (member($keypressed, (SDLK_RETURN, SDLK_SPACE, SDLK_KP_ENTER))) {
1764
play_sound('menu_selected');
1765
push @menu_invalids, $current_pos;
1766
foreach my $m (keys %menu_entries) {
1767
if ($menu_entries{$m}->{pos} == $current_pos) {
1768
if ($menu_entries{$m}->{type} =~ /^run/) {
1769
$menu_entries{$m}->{run}->();
1770
$menu_entries{$m}->{type} eq 'rungame' and $start_game = 1;
1772
if ($menu_entries{$m}->{type} eq 'toggle') {
1773
$menu_entries{$m}->{value} = !$menu_entries{$m}->{value};
1774
if ($menu_entries{$m}->{value}) {
1775
$menu_entries{$m}->{act}->() or $menu_entries{$m}->{value} = 0;
1777
$menu_entries{$m}->{unact}->() or $menu_entries{$m}->{value} = 1;
1780
if ($menu_entries{$m}->{type} eq 'range') {
1781
$menu_entries{$m}->{value}++;
1782
$menu_entries{$m}->{value} > $menu_entries{$m}->{valuemax}
1783
and $menu_entries{$m}->{value} = $menu_entries{$m}->{valuemin};
1784
$menu_entries{$m}->{change}->($menu_entries{$m}->{value});
1790
$keypressed == SDLK_ESCAPE and exit 0;
1795
if ($graphics_level > 1) {
1796
my $banner_pos if 0;
1797
$banner_pos ||= 670;
1798
foreach my $b (keys %banners) {
1799
my $xpos = $banners{$b} - $banner_pos;
1800
my $image = $imgbin{"banner_$b"};
1802
$xpos > $banners_max/2 and $xpos = $banners{$b} - ($banner_pos + $banners_max);
1804
if ($xpos < $BANNER_MAXX && $xpos + $image->width >= 0) {
1805
my $irect = new SDL::Rect(-width => min($image->width+$xpos, $BANNER_MAXX-$BANNER_MINX), -height => $image->height, -x => -$xpos);
1806
$image->blit($irect, $app, new SDL::Rect(-x => $BANNER_MINX, '-y' => $BANNER_Y));
1810
$banner_pos >= $banners_max and $banner_pos = 1;
1812
$app->update($banner_rect);
1814
my $to_wait = $TARGET_ANIM_SPEED - ($app->ticks() - $synchro_ticks);
1815
$to_wait > 0 and $app->delay($to_wait);
1818
#- for $KEYS, try hard to keep SDLK_<key> instead of integer value in rcfile
1820
foreach my $p (keys %$KEYS) {
1821
foreach my $k (keys %{$KEYS->{$p}}) {
1822
eval("$KEYS->{$p}->{$k} eq SDLK_$_") and $KEYS_->{$p}->{$k} = "SDLK_$_" foreach @fbsyms::syms;
1825
my $dump = Data::Dumper->Dump([$fullscreen, $graphics_level, $KEYS_], [qw(fullscreen graphics_level KEYS)]);
1826
$dump =~ s/'SDLK_(\w+)'/SDLK_$1/g;
1827
output($rcfile, $dump);
1830
!is_1p_game() and $pdata{$::p}{score} = 0;
1832
play_music(is_1p_game() ? 'main1p' : 'main2p');
1837
#- ----------- main -------------------------------------------------------
1847
eval { maingame() };
1849
if ($@ =~ /^new_game/) {
1851
} elsif ($@ =~ /^quit/) {