##########################################################################
package Games::PangZero::PlayableGameBase;
##########################################################################
@ISA = qw( Games::PangZero::GameBase );
use strict;
use warnings;
use SDL::Video;
sub new {
my ($class) = @_;
my $self = Games::PangZero::GameBase->new();
%{$self} = (%{$self},
'playersalive' => 0,
'level' => 0,
'backgrounds' => [ qw( desert2.png l1.jpg l2.jpg l3.jpg l4.jpg l5.jpg l6.jpg l7.jpg l8.jpg l9.jpg )],
);
bless $self, $class;
}
sub ResetGame {
my $self = shift;
$self->SUPER::ResetGame();
$self->{playersalive} = 0;
$Games::PangZero::GamePause = 0;
foreach my $player (@Games::PangZero::Players) {
last if $player->{number} >= $Games::PangZero::NumGuys;
$self->SpawnPlayer($player);
}
$self->SetGameLevel(0);
$self->LayoutScoreBoard();
push @Games::PangZero::GameObjects, (Games::PangZero::FpsIndicator->new());
}
sub SetGameSpeed {
my $self = shift;
$Games::PangZero::GameSpeed = 0.8 * $Games::PangZero::DifficultyLevel->{speed};
}
sub SetGameLevel {
my ($self, $level) = @_;
$self->{level} = $level;
if (($level % 10) == 9) {
$self->SetBackground( int($level / 10) + 1 );
}
$self->SetGameSpeed();
}
sub SpawnPlayer {
my ($self, $player) = @_;
$player->{score} = 0;
$player->{scoreforbonuslife} = 200000;
$player->{lives} = 2;
$player->{startX} = ($Games::PangZero::ScreenWidth - $Games::PangZero::NumGuys * 60) / 2 + 60 * ($player->{number}+0.5) - 32;
$player->{respawn} = -1;
my $guy = Games::PangZero::Guy->new($player);
push @Games::PangZero::GameObjects, ($guy);
++$self->{playersalive};
return $guy;
}
sub AdvanceGameObjects {
my ($self) = @_;
$self->SUPER::AdvanceGameObjects();
$self->RespawnPlayers();
--$Games::PangZero::GamePause if $Games::PangZero::GamePause > 0;
}
sub RespawnPlayers {
my $self = shift;
foreach my $player (@Games::PangZero::Players) {
last if $player->{number} >= $Games::PangZero::NumGuys;
if ($player->{respawn} > 0) {
--$player->{respawn};
$player->{score} = int($player->{respawn} / 100) if $self->{playersalive};
if ($player->{respawn} <= 0) {
my $guy = $self->SpawnPlayer($player);
$guy->{invincible} = 500;
}
}
}
}
sub PlayerNextLife {
my ($self, $guy) = @_;
$guy->DeleteHarpoons;
if ($guy->{player}->{lives}--) {
$guy->{x} = $guy->{player}->{startX};
$guy->{y} = $Games::PangZero::ScreenHeight - $guy->{h};
$guy->{state} = 'idle';
$guy->{speedY} = $guy->{speedX} = 0;
$guy->{invincible} = 500; # 0.5s
$guy->{killed} = 0;
$guy->{justkilled} = 0;
$self->{playerspawned} = 1;
} else {
# One player less
Games::PangZero::Highscore::AddHighScore($guy->{player}, $guy->{player}->{score}, $self->{level} + 1);
$guy->Delete();
--$self->{playersalive};
$guy->{player}->{respawn} = 6000; # 60s
}
}
sub PlayerDeathSequence {
my $self = shift;
my (@killedGuys, @deadGuys, $guy, $i);
$self->DrawGame();
Games::PangZero::Music::PlaySound('death');
Games::PangZero::Graphics::RenderBorder($Games::PangZero::WhiteBorderSurface, $Games::PangZero::App);
SDL::Video::flip($Games::PangZero::App);
$self->Delay(10);
Games::PangZero::Graphics::RenderBorder($Games::PangZero::RedBorderSurface, $Games::PangZero::App);
Games::PangZero::Graphics::RenderBorder($Games::PangZero::RedBorderSurface, $Games::PangZero::Background);
SDL::Video::flip($Games::PangZero::App);
$self->Delay(90);
@killedGuys = grep { $_->{justkilled}; } @Games::PangZero::GameObjects;
foreach $guy (@killedGuys) {
$guy->Clear();
$guy->{killed} = 1;
push @deadGuys, (Games::PangZero::DeadGuy->new($guy));
}
push @Games::PangZero::GameObjects, (@deadGuys);
for ($i = 0; $i < 300; ++$i) {
Games::PangZero::HandleEvents();
return if $self->{abortgame};
my $advance = $self->CalculateAdvances();
while ($advance--) {
foreach my $gameObject (@deadGuys) {
$gameObject->Advance();
}
}
$self->DrawGame();
last if $deadGuys[0]->{deleted};
}
foreach $guy (@killedGuys) {
$self->PlayerNextLife($guy);
}
Games::PangZero::Graphics::RenderBorder($Games::PangZero::BorderSurface, $Games::PangZero::App);
Games::PangZero::Graphics::RenderBorder($Games::PangZero::BorderSurface, $Games::PangZero::Background);
}
sub SuperKill {
my ($self, $guy) = @_;
my @gameObjects = @Games::PangZero::GameObjects;
my $sound = 0;
foreach my $ball (@gameObjects) {
next unless $ball->isa("Games::PangZero::Ball");
$ball->Pop($guy, 'superkill');
$sound = 1;
}
Games::PangZero::Music::PlaySound('pop') if $sound;
}
sub PopEveryBall {
my $self = shift;
my @guys = ();
my @gameObjects = @Games::PangZero::GameObjects;
foreach (@gameObjects) {
if ($_->isa('Games::PangZero::Ball')) {
$_->Pop(undef, 'meltdown');
} elsif ('Games::PangZero::Guy' eq ref $_) {
push @guys, $_;
}
}
return @guys;
}
sub DeathballMeltdown {
my ($self) = @_;
my ($i, $allKilled, @guys, @killedGuys, @deadGuys);
$self->{nocollision} = 1;
my $meltdown = Games::PangZero::Meltdown->new();
push @Games::PangZero::GameObjects, $meltdown;
for ($i = 0; $i < 300; ++$i) {
%Games::PangZero::Events = ();
Games::PangZero::HandleEvents();
return if $self->{abortgame};
my $advance = $self->CalculateAdvances();
while ($advance--) {
# TODO REINSTATE THIS IN 1.1!!! $self->PreAdvanceAction(); # Hook for something special
$self->SUPER::AdvanceGameObjects();
$Games::PangZero::GamePause = 0;
if ($meltdown->{bounce} and not $allKilled) {
$allKilled = 1;
@guys = $self->PopEveryBall();
foreach (@guys) {
$_->{killed} = 1;
push @deadGuys, Games::PangZero::DeadGuy->new($_);
push @killedGuys, $_;
}
push @Games::PangZero::GameObjects, (@deadGuys);
}
}
$self->DrawGame();
}
foreach (@killedGuys) {
$self->PlayerNextLife($_);
}
$self->{nocollision} = 0;
}
##########################################################################
# GAME DRAWING
##########################################################################
sub DrawScoreBoard {
my ($self) = @_;
$self->DrawLevelIndicator( 10, $self->{scoreBoardTop} );
for (my $i = 0; $i < $Games::PangZero::NumGuys; ++$i) {
$self->DrawScore( $Games::PangZero::Players[$i], $Games::PangZero::Players[$i]->{scoreX}, $Games::PangZero::Players[$i]->{scoreY} );
}
}
sub LayoutScoreBoard {
my ($self) = @_;
my $scoreBoardTop = $Games::PangZero::ScreenHeight + $Games::PangZero::ScreenMargin * 2 + 5;
my $scoreBoardHeight = $Games::PangZero::PhysicalScreenHeight - $scoreBoardTop;
my $rowHeight = 64;
my $leftMargin = 150;
my $rows = $Games::PangZero::NumGuys > 4 ? 2 : 1;
$rows = 1 if ($scoreBoardTop + $rows * $rowHeight > $Games::PangZero::PhysicalScreenHeight);
if ($scoreBoardTop + $rows * $rowHeight > $Games::PangZero::PhysicalScreenHeight) {
$rowHeight = 32;
$scoreBoardTop = $Games::PangZero::PhysicalScreenHeight - 32;
}
my $guysPerRow = int ($Games::PangZero::NumGuys / $rows + 0.5);
my $widthPerGuy = ($Games::PangZero::PhysicalScreenWidth - $leftMargin) / $guysPerRow;
for (my $i = 0; $i < $Games::PangZero::NumGuys; ++$i) {
$Games::PangZero::Players[$i]->{scoreX} = $leftMargin + ($i % $guysPerRow) * $widthPerGuy;
$Games::PangZero::Players[$i]->{scoreY} = $scoreBoardTop + int ($i / $guysPerRow) * $rowHeight;
$Games::PangZero::Players[$i]->{scoreRect} =
SDL::Rect->new($Games::PangZero::Players[$i]->{scoreX}, $Games::PangZero::Players[$i]->{scoreY}, 130, $rowHeight);
}
$self->{scoreBoardTop} = $scoreBoardTop;
$self->{scoreBoardHeight} = $scoreBoardHeight;
$self->{rowHeight} = $rowHeight;
}
sub DrawLevelIndicator {
my ($self, $x, $y) = @_;
$self->{levelIndicatorRect} = SDL::Rect->new($x, $y, 100, 32) unless $self->{levelIndicatorRect};
SDL::Video::fill_rect($Games::PangZero::App, $self->{levelIndicatorRect}, SDL::Video::map_RGB($Games::PangZero::App->format(), 0, 0, 0));
SDLx::SFont::print_text( $Games::PangZero::App, $x, $y + 3, 'Level ' . ($self->{level}+1) );
}
sub PrintNumber {
my ($self, $player, $x, $y, $number) = @_;
my $numberText = sprintf("%d", $number);
my $srcrect = SDL::Rect->new(0, 160, 16, 16);
my $dstrect = SDL::Rect->new($x, $y, 16, 16);
for (my $i = 0; $i < length($numberText); ++$i) {
$srcrect->x(320 + (ord(substr($numberText, $i)) - ord('0')) * 16);
$dstrect->x($x + $i * 16);
SDL::Video::blit_surface($player->{guySurface}, $srcrect, $Games::PangZero::App, $dstrect );
}
}
sub DrawScore {
my ($self, $player, $x, $y, $livesY) = @_;
SDL::Video::fill_rect($Games::PangZero::App, $player->{scoreRect}, SDL::Video::map_RGB($Games::PangZero::App->format(), 0, 0, 0));
$self->PrintNumber( $player, $x, $y, $player->{score});
$livesY = $self->{rowHeight} > 32 ? $y + 24 : $y + 16;
my $dstrect = SDL::Rect->new($x, $livesY, 32, 32);
my $srcrect = ($self->{rowHeight} <= 32)
? SDL::Rect->new(320, 176, 16, 16)
: SDL::Rect->new(320, 128, 32, 32);
if ($player->{lives} > 3) {
SDL::Video::blit_surface($player->{guySurface}, $srcrect, $Games::PangZero::App, $dstrect );
$self->PrintNumber( $player, $x + $srcrect->w() + 8, $livesY + ($srcrect->h() - 16 ) / 2, $player->{lives} );
} else {
foreach my $i ( 0 .. $player->{lives}-1 ) {
$dstrect->x( $x + $i * ($srcrect->w() + 4) );
SDL::Video::blit_surface($player->{guySurface}, $srcrect, $Games::PangZero::App, $dstrect );
}
}
}
sub PreAdvanceAction {}
sub AdvanceGame {
my $self = shift;
%Games::PangZero::GameEvents = ();
$self->PreAdvanceAction(); # Hook for something special
if ($self->{superKillCount} > 0) {
if (--$self->{superKillDelay} <= 0) {
--$self->{superKillCount};
$self->{superKillDelay} = 50;
$self->SuperKill($self->{superKillGuy});
}
$Games::PangZero::GamePause = 0;
}
$self->AdvanceGameObjects();
if ($Games::PangZero::GameEvents{earthquake}) {
Games::PangZero::Music::PlaySound('quake');
foreach my $guy (@Games::PangZero::GameObjects) {
$guy->Earthquake($Games::PangZero::GameEvents{earthquake}) if ref $guy eq 'Games::PangZero::Guy';
}
}
if ($Games::PangZero::GameEvents{'pop'}) {
Games::PangZero::Music::PlaySound('pop');
}
if ($Games::PangZero::GameEvents{meltdown} and $Games::PangZero::DifficultyLevel->{name} ne 'Miki') {
$self->DeathballMeltdown();
} elsif ($Games::PangZero::GameEvents{kill} ) {
$self->PlayerDeathSequence();
return if $self->{playersalive} <= 0;
$Games::PangZero::GamePause = 200 if $Games::PangZero::GamePause < 200;
Games::PangZero::GamePause::Show();
} elsif ($Games::PangZero::GameEvents{magic}) {
if ($Games::PangZero::GamePause < 200) {
$Games::PangZero::GamePause = 200;
Games::PangZero::Music::PlaySound('pause');
Games::PangZero::GamePause::Show();
}
} elsif ($Games::PangZero::GameEvents{superpause}) {
if ($Games::PangZero::GamePause < 800) {
$Games::PangZero::GamePause = 800;
Games::PangZero::Music::PlaySound('pause');
Games::PangZero::GamePause::Show();
}
} elsif ($Games::PangZero::GameEvents{superkill}) {
$self->{superKillCount} = 5;
$self->{superKillDelay} = 0;
$self->{superKillGuy} = $Games::PangZero::GameEvents{superkillguy};
$self->{spawndelay} = 250;
$self->{superballdelay} += 1000; # 10 second penalty
my @gameObjects = @Games::PangZero::GameObjects;
foreach my $spawningBall (@gameObjects) {
$spawningBall->Delete if $spawningBall->{spawning};
}
}
}
sub Run {
my ($self) = shift;
$self->ResetGame();
Games::PangZero::GameTimer::ResetTimer();
$self->{superKillCount} = 0;
$self->{superKillDelay} = 0;
$self->{superKillGuy} = undef;
while (! $self->{abortgame}) {
# Calculate advance (how many game updates to perform)
my $advance = $self->CalculateAdvances();
# Advance the game
%Games::PangZero::Events = ();
Games::PangZero::HandleEvents();
while ($advance-- && ! $self->{abortgame}) {
$self->AdvanceGame();
}
if ($self->{playersalive} <= 0) {
my $gameoverSurface = SDL::Image::load("$Games::PangZero::DataDir/gameover.png");
my @gameObjects = @Games::PangZero::GameObjects;
foreach (@gameObjects) { $_->Delete() if ('Games::PangZero::DeadGuy' eq ref $_); }
$self->DrawGame();
SDL::Video::blit_surface($gameoverSurface, SDL::Rect->new(0, 0, $gameoverSurface->w, $gameoverSurface->h),
$Games::PangZero::App, SDL::Rect->new(
($Games::PangZero::PhysicalScreenWidth - $gameoverSurface->w) / 2, $Games::PangZero::PhysicalScreenHeight / 2 - 100,
$gameoverSurface->w, $gameoverSurface->h));
SDL::Video::flip($Games::PangZero::App);
SDL::delay(1000);
for (my $i=0; $i < 20; ++$i) {
SDL::delay(100);
%Games::PangZero::Events = ();
Games::PangZero::HandleEvents();
last if $self->{abortgame};
last if %Games::PangZero::Events;
}
last;
}
$self->DrawGame();
}
$self->SUPER::ResetGame();
}
1;