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

use strict;


=comment
Spawners are used primarily by the level objects to create enemies.
Each spawner had a number of actions (e.g. "Spawn 3 bees") and each action
has a probability weight associated with it. The probabilities are numbers
that have no special unit attached. If there are actions A, B and C with
probabilities 10, 20 and 30 respectively, then C will happen 50% of times,
B will happen 1/3 of times, and A will happen 1/6 of times.

The Roulette package is responsible for rolling a metaphorical roulette,
where each action is represented, weighted by its probability, and executing
one action.

This framework is used for making spawners:

&MakeEasySpawner            Bats Dougars Bees PangGuy
&MakeProjectileSpawner      spawns Bees and Dougars
&MakeModerateSpawner        spawns Bats, Dougars, Comets or Cloudkills at a moderate rate
&MakeHardSpawner            spawns Comets, CloudKills, Witches at a high rate
&MakeFullEasySpawner        spawns any enemy, easy enemies favored
&MakeFullSpawner            spawns any enemy, balanced mix
&MakeFullHardSpawner        spawns any enemy, hard enemies favored
&MakeTimeoutSpawner         spawns hard monsters at a high rate

Spawners take two parameters:
* Initial delay (e.g. 400, means nothing spawns for 4s)
* Speed (the larger values mean more rapid spawning)
=cut


use vars qw($Self);   # Used by the Roulette to make the outcomes simpler


##########################################################################
package Roulette;
##########################################################################

sub new {
  my $class = shift;
  my $self = { @_ };
  bless $self, $class;
}

sub SetOutcomes {
  my $self = shift;
  my ($sum, @outcomes, $outcome, $action, $probability, $from, $to);
  
  $sum = 0;
  while (@_) {
    $probability = shift;
    $action = shift;
    $to = $sum + $probability;
    $outcome = {
      'from' => $sum,
      'to' => $sum + $probability,
      'action' => $action,
    };
    push @outcomes, $outcome;
    $sum = $to;
  }
  $self->{sum} = $sum;
  $self->{outcomes} =\@outcomes;
}

sub Roll {
  my $self = shift;
  my ($rand, $low, $high, $mid, $outcome, $outcomes);
  
  Carp::confess("Nothing to roll.")  unless $self->{sum};
  $rand = $::Game->Rand($self->{sum});
  $outcomes = $self->{outcomes};
  $low = 0;
  $high = scalar(@$outcomes)-1;
  while ($low <= $high) {
    $mid = ($low + $high) >> 1;
    $outcome = $outcomes->[$mid];
    # print STDERR "Rolling(0..$rand..$self->{sum})... $low < $mid [$outcome->{from} .. $outcome->{to}] < $high\n";
    if ($rand < $outcome->{from}) {
      $high = $mid-1;
      next;
    }
    if ($rand >= $outcome->{to}) {
      $low = $mid+1;
      next
    }
    $::Self = $self;
    my $action = $outcome->{action};
    if (ref $action) {
      $action->();
    } else {
      &::EvalRouletteAction($action);
    }
    $::Self = undef;
    return;
  }
}


package main;

sub MakeSpawnerBase {
  my ($difficulty, $initialDelay, $speed) = @_;
  my $spawner = new Roulette(
    'isSpawner' => 1,
    'difficulty' => $difficulty,
    'delay' => $initialDelay || 0,
    'spawnCount' => 0,
    'sdelay' => 0,
    'speed' => $speed || 1,
    'advance' => \&AdvanceSpawner,
  );
  return $spawner;
}

sub AdvanceSpawner {
  my $self = shift;

  if ($self->{spawnCount} > 0) {
    return  if ($self->{sdelay} -= $self->{speed}) > 0;
    $self->{sdelay} = $self->{spawnDelay};
    --$self->{spawnCount};
    eval "new $self->{spawnClass}($self->{difficulty})";  die $@ if $@;
  }

  return  if ($self->{delay} -= $self->{speed}) > 0;
  $self->Roll();
}

sub EvalRouletteAction {
  eval($_[0]); die $@ if $@;
}

sub _SpawnDougars {
  my ($count, $orientation) = @_;
  foreach (1 .. ($count || 5)) { 
    new Dougar($_, $orientation, $Self->{difficulty});
  }
  $Self->{delay} = $Game->Rand(500) + 200;
};

sub _SpawnBats {
  my ($count) = @_;
  
  $Self->{spawnClass} = 'Bat';
  $Self->{spawnDelay} = 150;
  $Self->{spawnCount} = $count;
  $Self->{delay} = $count * 150 + $Game->Rand(500);
}

sub _SpawnFireBats {
  my ($count) = @_;
  
  $Self->{spawnClass} = 'FireBat';
  $Self->{spawnDelay} = 200;
  $Self->{spawnCount} = $count;
  $Self->{delay} = $count * 200 + $Game->Rand(700);
}

sub _SpawnBees {
  my ($count) = @_;
  
  $Self->{spawnClass} = 'Bee';
  $Self->{spawnDelay} = 150;
  $Self->{spawnCount} = $count;
  $Self->{delay} = $count * 150 + $Game->Rand(300);
}

sub _SpawnPangGuy {
  new PangGuy(($Game->Rand(2) ? -1 : 1), $Self->{difficulty});
  $Self->{delay} = 200 + $Game->Rand(200);
}

sub _SpawnComets {
  my ($count, $delay) = @_;
  
  $Self->{spawnClass} = 'Comet';
  $Self->{spawnDelay} = $delay;
  $Self->{spawnCount} = $count;
  $Self->{delay} = $Game->Rand(100) + 300;
}

sub _SpawnIceDarts {
  my ($count, $delay) = @_;
  
  $Self->{spawnClass} = 'IceDart';
  $Self->{spawnDelay} = $delay;
  $Self->{spawnCount} = $count;
  $Self->{delay} = $Game->Rand(100) + 300;
}

sub _SpawnCloudKills {
  my ($count, $delay) = @_;
  
  $Self->{spawnClass} = 'CloudKill';
  $Self->{spawnDelay} = $delay;
  $Self->{spawnCount} = $count;
  $Self->{delay} = $Game->Rand(300) + 400;
}

sub _SpawnWitch {
  new Witch(0, $Self->{difficulty});
  $Self->{delay} = 700 + $::Game->Rand(300);
}

sub _SpawnEasyWitch {
  new Witch(0, $Self->{difficulty} / 2);
  $Self->{delay} = 700 + $::Game->Rand(300);
}

sub _SpawnWitchFromLeft {
  new Witch(1, $Self->{difficulty});
  $Self->{delay} = 700 + $::Game->Rand(400);
}

sub _SpawnBouncyMoon {
  new BouncyMoon($Self->{difficulty});
  $Self->{delay} = 300 + $::Game->Rand(200);
}

sub _SpawnFlameElemental {
  new FlameElemental($Self->{difficulty});
  $Self->{delay} = 700 + $::Game->Rand(300);
}

sub _SpawnFlameElementals {
  my ($count, $delay) = @_;
  
  $delay = $delay || 150;
  $Self->{spawnClass} = 'FlameElemental';
  $Self->{spawnDelay} = $delay;
  $Self->{spawnCount} = $count;
  $Self->{delay} = $Game->Rand(500) + 700 * $count;
}

sub _SpawnGhostSwarm {
  new GhostSwarm($Self->{difficulty});
  $Self->{delay} = 300 + $::Game->Rand(200);
}

sub _SpawnSeekerBot {
  new SeekerBot($Self->{difficulty});
  $Self->{delay} = 500 + $::Game->Rand(300);
}

sub _SpawnSeekerBots {
  my ($count, $delay) = @_;
  
  $delay = $delay || 150;
  $Self->{spawnClass} = 'SeekerBot';
  $Self->{spawnDelay} = $delay;
  $Self->{spawnCount} = $count;
  $Self->{delay} = $Game->Rand(700) + 400 * $count;
}

sub _SpawnSpider {
  new Spider($Self->{difficulty});
  $Self->{delay} = 100 + $::Game->Rand(300);
}

sub _SpawnBomb {
  new Bomb($Self->{difficulty});
  $Self->{delay} = 300 + $::Game->Rand(300);
}

sub _SpawnMiscKoules {
  my ($count) = @_;
  foreach ( 1 .. $count) {
    new Koules(64);
    new Koules(48);
    new Koules(32);
  }
  $Self->{delay} = $count * 3 * 450 + $Game->Rand(500);
}

sub _SpawnSmallKoules {
  my ($count) = @_;
  foreach ( 1 .. $count) {
    new Koules(32);
  }
  $Self->{delay} = $count * 350 + $Game->Rand(500);
}

sub _SpawnMediumKoules {
  my ($count) = @_;
  foreach ( 1 .. $count) {
    new Koules(48);
  }
  $Self->{delay} = $count * 350 + $Game->Rand(500);
}

sub _SpawnLargeKoules {
  my ($count) = @_;
  foreach ( 1 .. $count) {
    new Koules(64);
  }
  $Self->{delay} = $count * 350 + $Game->Rand(500);
}


sub MakeEasySpawner {
  my $spawner = &MakeSpawnerBase(@_);
  $spawner->SetOutcomes(
    20 => '_SpawnDougars(4)',
    20 => '_SpawnDougars(4, 1)',
    30 => '_SpawnBats(2)',
    30 => '_SpawnBees(3)', 
     5 => '_SpawnGhostSwarm()',
     5 => '_SpawnBouncyMoon()',
    10 => '_SpawnSpider()',
    20 => '_SpawnIceDarts(15, $::Game->Rand(20) + 40)',
    10 => '_SpawnSmallKoules(1)',
    10 => '_SpawnMediumKoules(1)',
# 10000 => '_SpawnBomb()',
# 10000 => '_SpawnSeekerBots(2)',
#    1000 => '_SpawnFireBats(3)',
#    1000 => '_SpawnPangGuy()',
#    2000 => '_SpawnWitch()',
#    1000 => '_SpawnBouncyMoon()',
#    10000 => '_SpawnFlameElemental()',
#    10000 => 'new MotherCloud(); $Self->{delay} = 10000;'
#    10000 => 'new HorrorMoon(); $Self->{delay} = 10000;'
  );
  return $spawner;
}

sub MakeModerateSpawner {
  my $spawner = &MakeSpawnerBase(@_);
  
  $spawner->SetOutcomes(
    20 => '_SpawnDougars(5)',
    20 => '_SpawnDougars(5, 1)',
    20 => '_SpawnDougars(5); _SpawnDougars(5,1)',
    30 => '_SpawnBats(3)',
    30 => '_SpawnBees(4)', 
    20 => '_SpawnPangGuy()',
    20 => '_SpawnGhostSwarm()',
    30 => '_SpawnComets(15, $::Game->Rand(20) + 30)',
    15 => '_SpawnIceDarts(15, $::Game->Rand(20) + 30)',
    20 => '_SpawnCloudKills(10, 100)',
    20 => '_SpawnSeekerBot()',
    10 => '_SpawnSmallKoules(3)',
    10 => '_SpawnMediumKoules(3)',
     5 => '_SpawnLargeKoules(2)',
  );
  return $spawner;
}

sub MakeFullEasySpawner {
  my $spawner = &MakeSpawnerBase(@_);
  
  $spawner->SetOutcomes(
    15 => '_SpawnDougars(5)',
    15 => '_SpawnDougars(5, 1)',
    10 => '_SpawnDougars(5); _SpawnDougars(5,1)',
    30 => '_SpawnBats(3)',
    10 => '_SpawnFireBats(1)',
    35 => '_SpawnBees(3)',
    20 => '_SpawnPangGuy()',
    15 => '_SpawnComets(15, $::Game->Rand(20) + 40)',
    15 => '_SpawnIceDarts(15, $::Game->Rand(20) + 40)',
    15 => '_SpawnCloudKills(10, 100)',
    10 => '_SpawnEasyWitch();',
    20 => '_SpawnGhostSwarm()',
    20 => '_SpawnBouncyMoon();',
    15 => '_SpawnFlameElemental();',
    10 => '_SpawnSeekerBot()',
     5 => '_SpawnSeekerBots(2)',
     5 => '_SpawnBomb()',
    20 => '_SpawnSmallKoules(2)',
    10 => '_SpawnMediumKoules(2)',
     5 => '_SpawnLargeKoules(2)',
  );
  return $spawner;
}

sub MakeFullModerateSpawner {
  my $spawner = &MakeSpawnerBase(@_);
  
  $spawner->SetOutcomes(
    15 => '_SpawnDougars(6)',
    15 => '_SpawnDougars(6, 1)',
    10 => '_SpawnDougars(6); _SpawnDougars(6,1)',
    30 => '_SpawnBats(3)',
    20 => '_SpawnFireBats(2)',
    35 => '_SpawnBees(4)', 
    20 => '_SpawnPangGuy()',
    15 => '_SpawnComets(15, $::Game->Rand(20) + 30)',
    10 => '_SpawnIceDarts(15, $::Game->Rand(20) + 30)',
    20 => '_SpawnCloudKills(12, 80)',
    20 => '_SpawnWitch();',
    20 => '_SpawnGhostSwarm()',
    20 => '_SpawnBouncyMoon();',
    20 => '_SpawnFlameElemental();',
    20 => '_SpawnSeekerBot()',
    10 => '_SpawnSeekerBots(2)',
     5 => '_SpawnBomb()',
    10 => '_SpawnSmallKoules(3)',
    10 => '_SpawnMediumKoules(3)',
     5 => '_SpawnLargeKoules(2)',
     5 => '_SpawnMiscKoules(1)',
  );
  return $spawner;
}

sub MakeFullHardSpawner {
  my $spawner = &MakeSpawnerBase(@_);
  
  $spawner->SetOutcomes(
    10 => '_SpawnDougars(8)',
    10 => '_SpawnDougars(8, 1)',
    10 => '_SpawnDougars(8); _SpawnDougars(8,1)',
    20 => '_SpawnBats(5)',
    20 => '_SpawnFireBats(4)',
    30 => '_SpawnBees(5)', 
    15 => '_SpawnPangGuy()',
    15 => '_SpawnComets(20, $::Game->Rand(20) + 20)',
    10 => '_SpawnIceDarts(20, $::Game->Rand(20) + 20)',
    20 => '_SpawnCloudKills(15, 60)',
    20 => '_SpawnWitch();',
    10 => '_SpawnWitchFromLeft();',
    20 => '_SpawnWitch();',
     5 => '_SpawnWitch(); _SpawnWitchFromLeft();',
    10 => '_SpawnGhostSwarm()',
    20 => '_SpawnBouncyMoon();',
    10 => '_SpawnFlameElemental();',
    20 => '_SpawnFlameElementals(2);',
    10 => '_SpawnFlameElementals(3);',
     5 => '_SpawnSeekerBot()',
    10 => '_SpawnSeekerBots(2)',
    10 => '_SpawnSeekerBots(3)',
    20 => '_SpawnBomb()',
     5 => '_SpawnSmallKoules(4)',
     5 => '_SpawnMediumKoules(3)',
    10 => '_SpawnLargeKoules(3)',
    10 => '_SpawnMiscKoules(1)',
  );
  return $spawner;
}

sub MakeHardSpawner {
  my $spawner = &MakeSpawnerBase(@_);
  
  $spawner->SetOutcomes(
    20 => '_SpawnFireBats(4)',
    15 => '_SpawnComets(20, $::Game->Rand(20) + 20)',
    10 => '_SpawnIceDarts(20, $::Game->Rand(20) + 20)',
    20 => '_SpawnCloudKills(15, 60)',
    20 => '_SpawnWitch();',
    10 => '_SpawnWitchFromLeft();',
    20 => '_SpawnWitch();',
     5 => '_SpawnWitch(); _SpawnWitchFromLeft();',
    10 => '_SpawnGhostSwarm()',
    10 => '_SpawnBouncyMoon();',
    10 => '_SpawnFlameElemental();',
    20 => '_SpawnFlameElementals(2);',
    10 => '_SpawnFlameElementals(3);',
    10 => '_SpawnSeekerBots(3)',
     5 => '_SpawnSeekerBots(5)',
    20 => '_SpawnBomb()',
    10 => '_SpawnMiscKoules(1)',
    10 => '_SpawnLargeKoules(3)',
  );
  return $spawner;
}

sub MakeProjectileSpawner {
  my $spawner = &MakeSpawnerBase(@_);
  
  $spawner->SetOutcomes(
    10 => '_SpawnDougars(4)',
    10 => '_SpawnDougars(4, 1)',
    10 => '_SpawnDougars(4); _SpawnDougars(4,1)',
    30 => '_SpawnBees(3)',
    20 => '_SpawnBouncyMoon();',
    10 => '_SpawnSeekerBot()',
    10 => '_SpawnSeekerBots(2)',
  );
  return $spawner;
}

sub MakeBatAndBeeSpawner {
  my $spawner = &MakeSpawnerBase(@_);
  
  $spawner->SetOutcomes(
    30 => '_SpawnBees(2)',
    20 => '_SpawnBats(2)',
  );
  return $spawner;
}

sub MakePangGuySpawner {
  my $spawner = &MakeSpawnerBase(@_);
  
  $spawner->SetOutcomes(
    30 => '_SpawnPangGuy()',
     1 => '_SpawnBees(5)',
  );
  return $spawner;
}



1;
