##########################################################################
# This file is part of Vacuum Magic
# Copyright (C) 2008 by UPi <upi at sourceforge.net>
##########################################################################

use strict;

=comment

== How does the Level relate to the Game? ==

The Game object provides structure for the overall game:

* Using the @GameObjects global, it advances and draws the game.
* Handles player input
* Keeps the game speed steady (with the help of the GameTimer).
* In one word, it takes care of the administrative part of the business.

The Level object provides behavior for the game:

* It sets the music, background, etc. for the game.
* It spawns enemies.
* Keeps track of live players, player respawning, etc.

The life of a Level object is short: it lasts until the player completes
the current level or the game is over. The Level object has two important
methods: PreAdvance and PostAdvance

* PreAdvance is called by the Game before all the GameObjects are advanced.
* PostAdvance is called by the Game after all the GameObjects are advanced.

Level objects typically use a number of microobjects to take care of
certain aspects. These microobjects are created by various Make* methods.

=cut


package Level;
package GameOverLevel;
package IntermissionLevel;
package GameLevel;
package BossLevel;
package TargetPracticeLevel;
package TutorialLevel;


package main;

use vars qw(@Levels);
our ($ScreenWidth);

##########################################################################
# LEVEL FACTORY
##########################################################################
# The Level Factory creates and manages the @Levels.
# At the beginning of the $Game, &InitLevelFactory is called, this
# creates the current set of levels.
# Normally the last level is the GameOverLevel, that never ends.
# NormalGame takes the @Levels over when it is created.

sub GetLevelSet {
  return $LevelSet;
}

sub InitLevelFactory {
  my ($levelSet) = @_;
  
  print STDERR "InitLevelFactory: $levelSet\n";  
  $LevelSet = $levelSet;
  if ($LevelSet eq 'NightmareFlight') {
    return @Levels = ( new NightmareFlightLevel() );
  }
  
  if ($LevelSet eq 'TargetPractice') {
    return @Levels = ( new TargetPracticeLevel(), new IntermissionLevel(), new GameOverLevel() );
  }
  
  if ($LevelSet eq 'Tutorial') {
    return @Levels = (new FakeLevel(level=>1, music=>'Level0', background=>0), &InitTutorialLevels() );
  }
  
  if ($LevelSet =~ m,Tutorial/(\d+),) {
    return @Levels = new TutorialLevel($1, 'innerPlayback');
  }
  
  if ($LevelSet eq 'Bosses') {
    my $i = 0;
    @Levels = (
      new FakeLevel(level=>1, music=>'Boss', background=>'rand'),
      (map { new BossLevel($_, ++$i) } @BossRegistry),
      new IntermissionLevel(),
      new GameOverLevel(),
    );
    return;
  }
  
  if ($LevelSet =~ /^Bosses\/(\w+)$/) {
    @Levels = (
      new FakeLevel(level=>1, music=>'Boss', background=>'rand'),
      new BossLevel($1, 1),
      new IntermissionLevel(),
      new GameOverLevel(),
    );
    return;
  }
  
  if ($LevelSet eq 'AsteroidsGame') {
    @Levels = (
      new FakeLevel(level=>1, music=>'Boss', background=>'rand'),
      (map { new BossLevel('Asteroids', $_+1, asteroidsLevel=>$_) } (0 .. $#Asteroids::Levels)),
      new IntermissionLevel(),
      new GameOverLevel(),
    );
    return;
  }
  
  if ($LevelSet eq 'PangZeroGame') {
    @Levels = (
      new FakeLevel(level=>1, music=>'Boss', background=>'rand'),
      (map { new BossLevel('PangZeroBoss', $_+1, pangzeroLevel=>$_) } (0 .. $#PangZeroBoss::Levels)),
      new IntermissionLevel(),
      new GameOverLevel(),
    );
    return;
  }
  
  my $startingLevel = 0;
  if ($LevelSet =~ /^NormalGame\/(\d+)$/) {
    $startingLevel = $1;
  }
  my $levelTemplate = \&LevelTemplate;
  $levelTemplate = \&QuickLevelTemplate  if $LevelSet eq 'QuickGame';
  
  @Levels = ();
  push @Levels, (
    $levelTemplate->(  1 => 4,   1 => 2.4, 'EasySpawner,200,10'  ),
    new BossLevel('ShootingRange', 5),
    $levelTemplate->(  6 => 9,  2.6 => 5, 'ModerateSpawner,200,6, EasySpawner,400,6'  ),
    new FakeLevel(music=>'Boss'),
    new BossLevel('HorrorMoon', 10),
    new IntermissionLevel()
  ) if $startingLevel < 10;
  
  push @Levels, (
    $levelTemplate->( 11 => 14,  3 => 4.4, 'ModerateSpawner,0,6, FullEasySpawner,200,6' ),
    new BossLevel('TrialByFire', 15),
    $levelTemplate->( 16 => 19,  4.6 => 6, 'FullModerateSpawner,200,8, ModerateSpawner,0,10' ),
    new FakeLevel(music=>'Boss'),
    new BossLevel('RoboWitch', 20),
    new IntermissionLevel()
  ) if $startingLevel < 20;
    
  push @Levels, (
    $levelTemplate->( 21 => 24,  3 => 5, 'FullEasySpawner,200,8, EasySpawner,0,8' ),
    new BossLevel('CrossWind', 25),
    $levelTemplate->( 26 => 29,  5 => 7, 'FullModerateSpawner,0,8, EasySpawner,200,8' ),
    new FakeLevel(music=>'Boss'),
    new BossLevel('MotherCloud', 30),
    new IntermissionLevel(),
  ) if $startingLevel < 30;
  
  push @Levels, (
    $levelTemplate->( 31 => 34,  4 => 6, 'FullModerateSpawner,200,8, ModerateSpawner,0,10'),
    new BossLevel('PangZeroBoss', 35),
    $levelTemplate->( 36 => 39,  6 => 8, 'FullHardSpawner,0,10, EasySpawner,100,10'),
    new FakeLevel(music=>'Boss'),
    new BossLevel('RedDragon', 40),
    new IntermissionLevel(),
  ) if $startingLevel < 40;
  
  push @Levels, (
    $levelTemplate->( 41 => 44,  4 => 6.3, 'FullModerateSpawner,0,10, HardSpawner,200,8'),
    new BossLevel('WipeOut', 45),
    $levelTemplate->( 46 => 49,  6.7 => 9, 'FullModerateSpawner,0,10, HardSpawner,200,8'),
    new FakeLevel(music=>'Boss'),
    new BossLevel('PapaKoules', 50),
    new IntermissionLevel(),
  ) if $startingLevel < 50;
  
  push @Levels, (
    $levelTemplate->( 51 => 54,  5 => 6.8, 'FullEasySpawner,0,8, FullModerateSpawner,200,8, FullModerateSpawner,400,7'),
    new BossLevel('UpsideDown', 55),
    $levelTemplate->( 56 => 59,  7 => 9, 'FullEasySpawner,0,8, FullModerateSpawner,200,8, FullModerateSpawner,400,7'),
    new FakeLevel(music=>'Boss'),
    new BossLevel('BlueDragon', 60),
    new IntermissionLevel(),
  ) if $startingLevel < 60;
  
  push @Levels, (
    $levelTemplate->( 61 => 64,  5 => 6.8, 'FullEasySpawner,0,8, FullModerateSpawner,200,8, FullHardSpawner,400,7'),
    new BossLevel('Gravity', 65),
    $levelTemplate->( 66 => 69,  7 => 9, 'FullEasySpawner,0,8, FullModerateSpawner,200,8, FullHardSpawner,400,7'),
    new FakeLevel(music=>'Boss'),
    new BossLevel('Bomber', 70),
    new IntermissionLevel(),
  ) if $startingLevel < 70;
  
  push @Levels, (
    $levelTemplate->( 71 => 80,  6 =>10, 'ProjectileSpawner,0,12, FullModerateSpawner,200,10, FullHardSpawner,400,7'),
    new IntermissionLevel(),
  ) if $startingLevel < 80;
  
  push @Levels, (
    $levelTemplate->( 81 => 90,  7 =>11, 'FullModerateSpawner,0,10, FullHardSpawner,0,10, FullHardSpawner,400,10'),
    new IntermissionLevel(),
  ) if $startingLevel < 90;
  
  push @Levels, (
    $levelTemplate->( 91 =>100,  7 =>13, 'HardSpawner,200,10, FullModerateSpawner,0,10, FullModerateSpawner,0,10, FullHardSpawner,400,10'),
    new IntermissionLevel(),
    new GameOverLevel(),
  );
}

sub LevelTemplate {
  my ($from, $to, $difficultyFrom, $difficultyTo, $spawners) = @_;
  my ($i, $d, @result);
  
  return new GameLevel($from, $difficultyFrom, $spawners)  if $to <= $from;
  for ($i = $from; $i <= $to; ++$i) {
    $d = $difficultyFrom + ($difficultyTo - $difficultyFrom) * ($i-$from) / ($to-$from);
    # print STDERR "Creating GameLevel($i, $d, $spawners);\n";
    push @result, new GameLevel($i, $d, $spawners);
  }
  return @result;
}

sub QuickLevelTemplate {
  my ($from, $to, $difficultyFrom, $difficultyTo, $spawners) = @_;
  return new GameLevel($from, ($difficultyFrom + $difficultyTo) / 2, $spawners);
}


##########################################################################
# Level microobjects
##########################################################################

sub MakePlayerDeathHandler {
  return { 'advance' => \&AdvancePlayerDeathHandler, };
}

sub AdvancePlayerDeathHandler {
  my ($self) = @_;
  
  foreach my $player (@Players[0 .. $NumGuys-1]) {
    next  unless $player->{respawnDelay};
    if ( 0 == --$player->{respawnDelay} ) {
      $player->Reset();
    }
  }
  
  return unless $GameEvents{playerkilled};
  foreach my $playerNumber (keys %{$GameEvents{playerkilled}}) {
    my $player = $Players[$playerNumber];
    if ($player->{lives} > 0) {
      $Game->AddAction( &MakePlayerRespawn($player) );
      $Level->OnPlayerKilled();
    } else {
      $player->{lives} = -1;
      if (&IsGameOver()) {
        $Game->OnGameOver();
      } else {
        $player->{respawnDelay} = 6000;
      }
    }
  }
}

sub IsGameOver {
  foreach (0.. $NumGuys-1) {
    return 0  if $Players[$_]->{lives} >= 0;
  }
  return 1;
}

sub MakePlayerRespawn {
  my ($player) = @_;
  return { 'advance' => \&AdvancePlayerRespawn, 'delay' => 120, 'player' => $player };
}

sub AdvancePlayerRespawn {
  my ($self) = @_;
  if (--$self->{delay} < 0) {
    --$self->{player}->{lives}  unless $Cheat;
    my $guy = new Guy(player=>$self->{player});
    $guy->Spawn();
    $Game->RemoveAction($self);
    $Game->OnPlayerRespawn();
    $Level->OnPlayerRespawn();
  }
}

sub MakeSuperBallSpawner {
  return { 'advance' => \&AdvanceSuperBallSpawner, 'delay' => 2000 * &::GetDifficultyMultiplier(), 'name' => 'SuperBallSpawner' };
}

sub AdvanceSuperBallSpawner {
  my ($self) = @_;
  return  if --$self->{delay} > 0;
  
  if (&IsGameOver()) {
    $self->{advance} = \&AdvanceNothing;
    return;
  }
  new SuperBall;
  $self->{delay} = 6000 * &GetDifficultyMultiplier(); # 60 sec
}

sub MakeBonusBoxSpawner {
  return { 'advance' => \&AdvanceBonusBoxSpawner, 'delay' => 200, 'name' => 'BonusBoxSpawner' };
}

sub AdvanceBonusBoxSpawner {
  my ($self) = @_;
  return  if --$self->{delay} > 0;
  
  if (&IsGameOver()) {
    $self->{advance} = \&AdvanceNothing;
    return;
  }
  new BonusBox;
  $self->{delay} = (2000 + 7650 / $::NumGuys) * &GetDifficultyMultiplier(); # 96 sec, 58 sec, 45 sec, 39 sec
}

sub AdvanceNothing {}



##########################################################################
package Level;
##########################################################################

sub new {
  my $class = shift;
  
  my $self = {
    'timer' => 0,
    'actions' => [],
    'timeLeft' => 6000,
    'params' => [@_],
  };
  bless $self, $class;
  return $self;
}

sub Initialize {
}

sub RemoveAction {
  my ($self, $action) = @_;
  
  for (my $i = 0; $i < scalar @{$self->{actions}}; ++$i) {
    if ($self->{actions}->[$i] eq $action) {
      splice @{$self->{actions}}, $i, 1;
      return;
    }
  }
}

sub AddAction {
  my $self = shift;
  push @{$self->{actions}}, @_;
}

sub PreAdvance {
  my ($self) = @_;
  ++$self->{timer};
}

sub PostAdvance {
  my ($self) = @_;
  my @actions = @{$self->{actions}};
  foreach (@actions) {
    $_->{advance}->($_);
  }
}

sub OnPlayerKilled {
}

sub OnPlayerRespawn {
}

sub IsLevelOver {
  0;
}

sub OnLevelOver {
}

sub MakeSpawners {
  my ($self, $spawners) = @_;
  my (@result, @s, $i, $spawner, $delay, $speed, $eval);
  
  @s = split /\s*,\s*/, $spawners;
  for ($i = 0; $i < scalar(@s); $ i+= 3) {
    $spawner = $s[$i];
    $delay = $s[$i+1];
    $speed = $s[$i+2] / 10 * &::GetDifficultyMultiplier();
    # print STDERR "  Making spawner $spawner; difficulty = $self->{difficulty}; delay = $delay; speed = $speed\n";
    $eval = "push \@result, &::Make$spawner(\$self->{difficulty}, \$delay, \$speed);";
    eval($eval);  Carp::confess "$eval: $@" if $@;
  }
  push @{$self->{actions}}, @result;
}

sub DeleteSpawners {
  my ($self) = @_;
  
  @{$self->{actions}} = grep {not exists($_->{isSpawner})} @{$self->{actions}};
}


##########################################################################
package GameOverLevel;
##########################################################################

@GameOverLevel::ISA = qw(Level);

sub Initialize {
  my ($self) = @_;
  my ($level) = @{$self->{params}};
  
  $::Game->FadeMusic(200);
  $self->{level} = $level || $::Level->{level};
  $self->{timeLeft} = $::Level->{timeLeft};
  $self->{timeLeft} = 1  if $self->{timeLeft} <= 0;
  $::Game->RemoveActionByName('BonusBoxSpawner');
  $::Game->RemoveActionByName('SuperBallSpawner');
  $::Difficulty = 0;
}

sub PostAdvance {
  my ($self) = @_;
  
  $self->SUPER::PostAdvance();
  if ($self->{timer} > 100) {
    $::Game->{abortgame} = 1  if %::Events;
  }
  if ($self->{timer} == 300) {
    $::Game->SetMusic('GameOver');
    &::StopRecorder();
  }
}


##########################################################################
package IntermissionLevel;
##########################################################################

@IntermissionLevel::ISA = qw(Level);

sub Initialize {
  my ($self) = @_;
  my ($level, $complete, $completeText);
  
  $self->{level} = $::Level->{level};
  $self->{timeLeft} = $::Level->{timeLeft};
  $self->{timeLeft} =  1 if $self->{timeLeft} <= 0;
  $completeText = $::Level->{forceIntermissionText} || ::Ts("LEVEL %s COMPLETE!", $self->{level});
  $complete = new TextGameObject(text=>$completeText, font=>$::GlossyFont, x=>250, y=>500);
  $complete->Center();
  $self->{tempObjects} = [ $complete ];
  push @{$self->{actions}}, &::MakePlayerDeathHandler();
  $::Game->FadeMusic(100);
  $::Difficulty = 0;
  $::Game->FadeOutBackground();
  $self->CreateLayout();
  foreach (@::GameObjects) {
    $_->Leave()  if ref($_) eq 'Ball';
    delete $_->{isboss};
  }
  $::Game->OnPlayerRespawn();
}

sub CreateLayout {
  my ($self) = @_;
  
  if ($::NumGuys > 3) {
    my $half = int($::NumGuys / 2 + 0.5);
    $self->CreateLayoutRow( 300, @::Players[0 .. $half-1] );
    $self->CreateLayoutRow( 100, @::Players[$half .. $::NumGuys -1] );
  } else {
    $self->CreateLayoutRow( 200, @::Players[0 .. $::NumGuys -1] );
  }
}

sub CreateLayoutRow {
  my ($self, $y, @players) = @_;
  my ($num, $w, $x0, $i);
  
  $num = scalar @players;
  $w = $num * 240 + ($num-1) * 30;
  $x0 = int(($ScreenWidth - $w) / 2);
  for ($i=0; $i<$num; ++$i) {
    $self->CreateLayoutElement($x0 + $i * 270, $y, $players[$i]);
  }
}

sub CreateLayoutElement {
  my ($self, $x, $y, $player) = @_;
  my ($guy);
  
  push @{$self->{tempObjects}},
    $::Game->NewMenuItem($x, $y+80, ::Ts('Food vacuumed: %s', $player->{ballsVacuumed}), 'color' => $player->{color}),
    $::Game->NewMenuItem($x, $y+40, ::Ts('Enemies vacuumed: %s', $player->{enemiesVacuumed}), 'color' => $player->{color}),
    $::Game->NewMenuItem($x, $y,    ::Ts('Enemies shot: %s', $player->{enemiesShot}), 'color' => $player->{color}),
  ;
  $guy = $player->{guy};
  if ($guy) {
    push @{$self->{guys}}, { 
      'targetX' => $x + 120 - 48,
      'targetY' => $y + 120,
      'speedX'  => 0,
      'speedY'  => 0,
      'x'       => $guy->{x},
      'y'       => $guy->{y},
      'player'  => $guy->{player},
    };
  }
}

sub PostAdvance {
  my ($self) = @_;
  my ($guyObject);
  
  $self->SUPER::PostAdvance();
  $::Game->PlaySound('intermission')  if $self->{timer} == 50;
  foreach my $guy (@{$self->{guys}}) {
    if ($guy->{speedX}*abs($guy->{speedX}/0.05)/2 + $guy->{x} < $guy->{targetX}) {
      $guy->{speedX} += 0.05;
    } else {
      $guy->{speedX} -= 0.05;
    }
    if ($guy->{speedY}*abs($guy->{speedY}/0.05)/2 + $guy->{y} < $guy->{targetY}) {
      $guy->{speedY} += 0.05;
    } else {
      $guy->{speedY} -= 0.05;
    }
    $guyObject = $guy->{player}->{guy};
    $guyObject->{x} = ($guy->{x} += $guy->{speedX});
    $guyObject->{y} = ($guy->{y} += $guy->{speedY});
    $guyObject->{collision} = 3;
  }
}

sub IsLevelOver {
  my ($self) = @_;
  return $self->{timer} > 500;
}

sub OnLevelOver {
  my ($self) = @_;
  
  foreach (@{$self->{tempObjects}}) {
    if (ref($_) eq 'MenuItem') {
      $_->HideAndDelete()
    } else {
      $_->Delete();
    }
  }
  $self->{tempObjects} = [];
  $::Game->FadeInBackground();
}


##########################################################################
package FakeLevel;
##########################################################################

@FakeLevel::ISA = qw(Level);

sub Initialize {
  my ($self) = @_;
  my (%params) = @{$self->{params}};
  my ($background);
  
  $self->{level} = $params{level} || $::Level->{level} || 1;
  $::Game->SetMusic($params{music})  if $params{music};
  if (defined($params{background})) {
    $background = $params{background};
    $background = int(rand(100))  if $background eq 'rand';
    $::Game->SetBackground($background);
  }
  $self->SUPER::Initialize();
}

sub IsLevelOver {
  return 1;
}

sub OnLevelOver {}


##########################################################################
package GameLevel;
##########################################################################

@GameLevel::ISA = qw(Level);

sub Initialize {
  my ($self) = @_;
  my ($number, $difficulty, $spawners) = @{$self->{params}};
  my ($area);
  
  $::Game->PlaySound('nextlevel')  if ($number % 10) != 1;
  $area = int(($number-1) / 10);
  $::Game->SetMusic("Level$area");
  $::Game->SetBackground($area);
  
  $self->SUPER::Initialize();
  $difficulty *= &::GetDifficultyMultiplier();
  $self->{difficulty} = $::Difficulty = $difficulty;
  $self->{level} = $number;
  $self->{timeLeft} = $self->{timeForLevel} = int(4800 / &::GetDifficultyMultiplier());
  $self->{bonusLeft} = 3000;
  new GameLevelIndicator(level=>::Ts("Level %s", $number), bonus=>$::Level->{bonus} || 0);
  my $numBalls = 3 + $::NumGuys * (2 + $::DifficultySetting);  # 7, 11, .., 27
  foreach (1..$numBalls) {
    new Ball($self->{difficulty});
  }
  push @{$self->{actions}},
    &::MakePlayerDeathHandler(),
  ;
  $self->MakeSpawners($spawners);
}

sub PostAdvance {
  my ($self) = @_;
  my $timeLeft;
  
  $self->SUPER::PostAdvance();
  --$self->{bonusLeft};
  $timeLeft = --$self->{timeLeft};
  
  $timeLeft = $timeLeft / 100;
  if ($timeLeft <= 6 and $timeLeft > 0 and $timeLeft == int($timeLeft)) {
    $::Game->PlaySound('timewarning');
  }
}

sub OnPlayerRespawn {
  my ($self) = @_;
  
  $self->{timeLeft} = $self->{timeForLevel};
  foreach (@{$self->{actions}}) {
    $_->{spawnCount} = 0  if $_->{spawnCount};
  }
}

sub IsLevelOver {
  return $Ball::Balls == 0;
}

sub OnLevelOver {
  my ($self) = @_;
  
  my $bonus = int($self->{bonusLeft} / 100) * 1000;
  $bonus = $bonus < 0 ? 0 : $bonus;
  foreach my $player (@::Players[0 .. $::NumGuys-1]) {
    $player->GiveScore($bonus);
  }
  $self->{bonus} = $bonus;
}


##########################################################################
package BossLevel;
##########################################################################

@BossLevel::ISA = qw(Level);

sub Initialize {
  my ($self) = @_;
  my ($bossName, $number, %params) = @{$self->{params}};
  my ($ballsToSpawn, $levelIndicatorText);
  
  $self->SUPER::Initialize();
  $self->{level} = $number || 1;
  $self->{timeLeft} = $self->{timeForLevel} = 9000;
  $self->{bonusLeft} = 3000;
  $self->{newBallsToSpawn} = 4;
  $self->{bossSpawnDelay} = 200;
  $self->{bossName} = $bossName;
  $self->{difficulty} = $::Difficulty = eval("\$${bossName}::LevelDifficulty") * &::GetDifficultyMultiplier();
  %$self = (%$self, %params);  # Allow overrides in constructor
  $ballsToSpawn = 7;
  
  $levelIndicatorText = eval("\$${bossName}::LevelName");
  $levelIndicatorText = '' if ($::Level->{bossName} and $::Level->{bossName} eq $self->{bossName});
  new GameLevelIndicator(level=>$levelIndicatorText, bonus=>$::Level->{bonus} || 0);
  
  if ($bossName eq 'Gravity') {
    $ballsToSpawn = 0;
    $self->{newBallsToSpawn} = 0;
  } elsif ($bossName eq 'UpsideDown' || $bossName eq 'WipeOut') {
    $ballsToSpawn = 0;
    $self->{newBallsToSpawn} = 0;
  } elsif ($bossName eq 'BlueDragon') {
    push @{$self->{actions}}, &::MakeBatAndBeeSpawner($::Difficulty, 200, 1.0);
  } elsif ($bossName eq 'Bomber') {
    $self->{newBallsToSpawn} = 7;
  }
  
  foreach (1..$ballsToSpawn) {
    new Ball($self->{difficulty})->{score} = 0;
  }
  push @{$self->{actions}}, &::MakePlayerDeathHandler();
}

sub PostAdvance {
  my ($self) = @_;
  my $timeLeft;
  
  if ($self->{bossSpawnDelay}) {
    if (0 == --$self->{bossSpawnDelay}) {
      $self->{boss} = eval("new $self->{bossName}");  Carp::confess $@ if $@;
    }
  }

  --$self->{bonusLeft};
  $timeLeft = --$self->{timeLeft}; 
  if ($timeLeft == 0) {
    my @guys = @Guy::Guys;
    foreach (@guys) {
      $_->Die('force');
    }
  }
  $timeLeft = $timeLeft / 100;
  if ($timeLeft <= 6 and $timeLeft > 0 and $timeLeft == int($timeLeft)) {
    $::Game->PlaySound('timewarning');
  }
  if ($Ball::Balls == 0) {
    foreach (1 .. $self->{newBallsToSpawn}) {
      new Ball($self->{difficulty})->{score} = 0;
    }
  }
  $self->SUPER::PostAdvance();
}

sub OnPlayerKilled {
  my ($self) = @_;
  
  if ($self->{boss} and $self->{boss}->can('OnPlayerKilled')) {
    $self->{boss}->OnPlayerKilled();
  }
}

sub OnPlayerRespawn {
  my ($self) = @_;
  
  $self->{timeLeft} = $self->{timeForLevel};
}

sub IsLevelOver {
  my ($self) = @_;
  
  return 0  if $self->{bossSpawnDelay};
  return $self->{boss}->{deleted};
}

sub OnLevelOver {
  my ($self) = @_;
  
  my $bonus = int($self->{bonusLeft} / 100) * 1000;
  $bonus = $bonus < 0 ? 0 : $bonus;
  foreach my $player (@::Players[0 .. $::NumGuys-1]) {
    $player->GiveScore($bonus);
  }
  $self->{bonus} = $bonus;
  $self->{boss} = undef;
  foreach (@::GameObjects) {
    $_->Leave()  if ref($_) eq 'Ball';
  }
}


##########################################################################
package NightmareFlightLevel;
##########################################################################

@NightmareFlightLevel::ISA = qw(Level);

sub Initialize {
  my ($self) = @_;
  
  $::Game->SetMusic('Level1');
  $::Game->SetBackground(1);

  $self->SUPER::Initialize();
  $self->{difficulty} = $self->{baseDifficulty} = $::Difficulty = 5 * &::GetDifficultyMultiplier();
  $self->{level} = 1;
  $self->{timeLeft} = 9900;
  $self->{tick} = 0;
  push @{$self->{actions}},
    &::MakePlayerDeathHandler(),
    &::MakeFullHardSpawner( 5, 0, 0.5 * &::GetDifficultyMultiplier()),
    &::MakeFullHardSpawner( 5, 100, 0.5 * &::GetDifficultyMultiplier()),
    &::MakeFullModerateSpawner( 5, 200, 0.5 * &::GetDifficultyMultiplier()),
  ;
}

sub PostAdvance {
  my ($self) = @_;
  
  ++$self->{tick};
  if (0 == $self->{tick} % 100) {
    # We want the difficulty to reach 10 in ~ 4 minutes
    # 4 minutes are 24000 ticks -> 240 adjustments here
    $::Difficulty += 0.02;
    $self->{difficulty} = $::Difficulty;
    foreach (@{$self->{actions}}) {
      $_->{difficulty} = $::Difficulty;
      $_->{speed} += 0.003  if $_->{speed};
    }
    $self->{level} = int(($::Difficulty - $self->{baseDifficulty}) * 10) + 1;
  }
  $self->SUPER::PostAdvance();
  while ($Ball::Balls < 5) { new Ball($self->{difficulty}); }
}

sub OnPlayerRespawn {
  my ($self) = @_;
  
  foreach (@{$self->{actions}}) {
    $_->{spawnCount} = 0  if $_->{spawnCount};
  }
}

sub IsLevelOver {
  return 0;
}

sub OnLevelOver {}


##########################################################################
package TargetPracticeLevel;
##########################################################################

@TargetPracticeLevel::ISA = qw(Level);

@TargetPracticeLevel::Levels = (
#    time num size hover speed
    [ 30,  1, 128,   0,   0 ],
    [ 10,  2, 100,   0,   0 ],
    [ 10,  2,  90, 0.5,   0 ],
    [ 15,  3,  80,   1,   0 ],
    [ 15,  3,  70,   0, 0.5 ],
    [ 20,  4,  64,   1,   1 ],
    [ 25,  5,  64,   1, 1.5 ],
    [ 30,  5,  56,   1,   2 ],
    [ 35,  6,  48, 1.5,   2 ],
    [ 40,  8,  32,   2,   3 ],
);

sub Initialize {
  my ($self) = @_;
  
  $::Game->SetMusic('Level0');
  $::Game->SetBackground(int(rand(100)));
  $::Game->RemoveActionByName('SuperBallSpawner');
  $::Game->RemoveActionByName('BonusBoxSpawner');

  $self->SUPER::Initialize();
  $self->{difficulty} = $::Difficulty = 1.5;
  $self->{level} = 0;
  $self->{isLevelOver} = 0;
  $self->{timeLeft} = 0;
  push @{$self->{actions}},
    &::MakePlayerDeathHandler(),
  ;
  $self->{spawn} = [ @TargetPracticeLevel::Levels ];
  $self->SpawnMoreBullseyes();
}

sub PostAdvance {
  my ($self) = @_;
  my ($timeLeft);
  
  $self->SUPER::PostAdvance();
  while ($Ball::Balls < 5) {
    new Ball($self->{difficulty})->{score} = 0;
  }
  $timeLeft = --$self->{timeLeft};
  
  $timeLeft = $timeLeft / 100;
  if ($timeLeft <= 6 and $timeLeft > 0 and $timeLeft == int($timeLeft)) {
    $::Game->PlaySound('timewarning');
  }
  if ($timeLeft < 0) {
    $self->{forceIntermissionText} = "Time's up!";
    $self->{isLevelOver} = 1;
  }
}

sub OnBullseyeDeleted {
  my ($self) = @_;
  my ($numBullseyes);
  
  return  if $self->{isLevelOver};
  $numBullseyes = grep { $_->isa('Bullseye') } @::GameObjects;
  return  if $numBullseyes;
  $self->SpawnMoreBullseyes();
}

sub SpawnMoreBullseyes {
  my ($self) = @_;
  my ($spawn, $time, $numBullseyes, @bullseyeParams, $bullseye);
  
  $spawn = shift @{$self->{spawn}};
  return $self->{isLevelOver} = 1  unless $spawn;

  ($time, $numBullseyes, @bullseyeParams) = @$spawn;
  $::Difficulty += 0.5;
  ++$self->{level};
  $self->{timeLeft} += $time * 100;
  foreach (1 .. $numBullseyes) {
    $bullseye = new Bullseye(@bullseyeParams);
    $bullseye->{score} = $self->{level} * 1000;
    $bullseye->{isboss} = 1;
    $bullseye->SetOnDeleted(\&OnBullseyeDeleted, $self);
  }
}

sub OnPlayerRespawn {
}

sub IsLevelOver {
  my ($self) = @_;
  return $self->{isLevelOver};
}

sub OnLevelOver {}

1;
