#!/usr/bin/perl -w

=comment

##########################################################################
#
# VACUUM MAGIC
# Copyright (C) 2008-2009 by UPi <upi at sourceforge.net>
#
##########################################################################

This program is free software; you can redistribute it and//or modify
it under the terms of the GNU General Public License version 2, as
published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.




DESCRIPTION

Vacuum Magic is a fast-paced action game for 1-6 players. The players have
to fly around vacuuming up food and hostiles, avoiding enemies and poisonous
food. Vacuumed food and enemies can be spat out, using them to blow up other
enemies. What makes the game different from other side-scrolling shootemups
is the fact that the player cannot shoot or otherwise directly attack monsters. 
He can only vacuum them up, or spit them at other enemies.


= Levels =
== Forest area ==
* Target Range
* Horror Moon
== Halloween ==
* Trial By Fire
* RoboWitch
== Mountain ==
* CrossWind
* MotherCloud
== Stars Over Mars ==
* PangZeroBoss
* RedDragon
== Blue Cave ==
* UpsideDown
* BlueDragon
== Starfield ==
* Gravity
* Bomber
== Arctic ==
== Hubble ==
== Cloudy ==

= TODO =

== Content ==

* Select background by name, rename FakeLevel to NewEnvironmentLevel

* Enemies
** Phantom enemy (moves towards the closest guy at a slowly increasing speed)
** Rock Phantom (cannot be slurped)
** Poison bat
** Firebee (shoots fireballs)
** Spider (clims down from ceiling on a thin line, the line can paralyze players. If the line is cut, the spider falls.)
** Evil Moon (throws boomerang-like objects, cannot be slurped)

* Boss fights
** Dodgeball
** Little Witch''s Big Night
** Big Dougar monster shooting little Dougars (only vulnerable to spitting)
** Wipe Out (the game area keeps getting smaller and smaller)
** Phantom of the Space Opera
** Nightmare! (Level 100)
** Black Dragon
** Gaia
** Wipe Out
** Fireworks
** Enemy: Phantom

* Levels
** Levels with only poisonous balls

== Extra features ==

* Score dependent on difficulty setting?
* Gameplay
** 0 game timer causes more random enemies to spawn
* High Score / High Level table

== Help ==

* "Shoot the magic box!" text
* Credits
* Tip of the day ("Did you know?")
** Configurable on/off

== Internal == 

* Fixes:
** OnPlayerRespawn vs OnKillEvent($evenIfBoss)
* Use fields / base to speed up GameObjects
* Use texture cache for MenuItems

== Backgrounds ==

BACKDROPS

* Cloudy (default background)
* Hyperspace from UQM
* Grand Canyon
* Hubble nebula
* Constellation art
* Desert


=cut


use strict;
use SDL;
use SDL::App;
use SDL::Event;
use SDL::Surface;
use SDL::Timer;
use SDL::Palette;
use SDL::Sound;
use SDL::Mixer;
use SDL::Font;
use SDL::OpenGL;
use Carp;

# SDL objects

use vars qw (
  $App $ScoreFont $BrandyFont $GlossyFont $MenuFont
  %Textures %Sprites
  %Sounds $Mixer
);

# Vacuum Magic variables and objects

use vars qw (
  $DataDir $BinDir $Version
  $Difficulty $DifficultySetting @DifficultySettingNames $TutorialPlayed
  $ScreenHeight $ScreenWidth $PhysicalScreenWidth $PhysicalScreenHeight
  $SoundEnabled $MusicEnabled $FullScreen $FpsIndicator $TimePrecisePlayback $MovieMode $Cheat $Language
  @GameObjects %GameEvents $GamePause $Game $Level $LevelSet
  @Players $NumGuys @BossRegistry
  %Translate $UnicodeMode $LastUnicodeKey %Keys %Events %MenuEvents
  @Sin @Cos
);



sub IsMicrosoftWindows {
  return $^O eq 'MSWin32';
}

BEGIN {
  $Version = '0.13';
  $DataDir = '';  # Set it to a path to avoid autodetection (e.g. /opt/vacuum/data)
  $BinDir = '';   # Set it to a path to avoid autodetection (e.g. /opt/vacuum/bin)

  if (&IsMicrosoftWindows()) {
    my $perlPath = $^X;
    $perlPath =~ s|\\|/|g;
    $perlPath =~ s|/[^/]+$||  or die;
    push @INC, $perlPath;
  }

  sub TestDataDir {
    return -f "$DataDir/bomb.wav";   # Should be a file from the latest version.
  }
  
  sub TestBinDir {
    return -f "$BinDir/recorder.pl";
  }
  
  sub FindDataDir {
    return if $DataDir and &TestDataDir();
    my @guesses = qw( . .. /usr/share/vacuum /usr/share/games/vacuum /usr/local/share/vacuum /opt/vacuum/ /opt/share/vacuum);
    my $programName = $0;
    $programName =~ s|[/\\][^/\\]+$||;
    unshift @guesses, $programName, "$programName/..";
    foreach my $guess (@guesses) {
      $DataDir = $guess;
      return  if &TestDataDir();
      $DataDir = "$guess/data";
      return  if &TestDataDir();
    }
    die "Couldn't find the data directory. Please set it manually.";
  }
  
  sub FindBinDir {
    return if $BinDir and &TestBinDir();
    my @guesses = ( $DataDir, '.', '..');
    my $programName = $0;
    $programName =~ s|[/\\][^/\\]+$||;
    unshift @guesses, $programName;
    foreach my $guess (@guesses) {
      $BinDir = $guess;
      return  if &TestBinDir();
      $BinDir = "$guess/bin";
      return  if &TestBinDir();
    }
    die "Couldn't find the bin directory. Please set it manually.\n(\$DataDir is $DataDir)";
  }

  &FindDataDir();
  &FindBinDir();
  push @INC, $BinDir;
  print "Data directory is at '$DataDir'\nBin directory is at '$BinDir'\n";
}


##########################################################################
# Utility subs
##########################################################################

sub RemoveFromList(\@$) {
  my ($list, $value) = @_;
  @$list = grep {$_ ne $value} @$list;
}

sub ConfessWithDump {
  &SaveRecord('error_record.txt')  if $::RecorderOn;
  open ERROR, ">error_desc.txt";
  print ERROR &GetObjectDump(), "\n", "\n", Carp::longmess(@_);
  close ERROR;
  Carp::confess(@_, &GetObjectDump());
}

sub GetDifficultyMultiplier {
  return 0.6 + $DifficultySetting * 0.2;   # easy = 0.6, moderate = 0.8, hard = 1.0, insane = 1.2
}


##########################################################################
# GLOBAL CONFIGURATION
##########################################################################

$NumGuys = 1;
foreach (0 .. 800) {
  $Sin[$_] = sin(6.283185*$_/800);
  $Cos[$_] = cos(6.283185*$_/800);
}

%Sounds = (
  'slurp'       => 'slurp.voc',
  'gulp'        => 'ag2.voc',
  'spit'        => 'spit.wav',
  'superspit'   => 'superspit.wav',
  'poisoned'    => 'poison.wav',
  'killed'      => 'killed.wav',
  'comethit'    => 'comet.wav',
  'harpoon'     => 'harpoon.voc',
  'harpoonhit'  => 'harpoonhit.voc',
  'bonuslife'   => 'bonuslife.wav',
  'witch'       => 'witch.wav',
  'enemydeath'  => 'enemydeath.wav',
  'playercollision' => 'playercollision.wav',
  'timewarning' => 'time.wav',
  'nextlevel'   => 'level.wav', # 60 7F 9B 9F
  'bonusitem'   => 'bonusitem.wav',
  'bonuslife'   => 'bonuslife.wav',
  'intermission'=> 'intermission.ogg',
  'protection'  => 'protection.wav',
  'bossdies'    => 'bossdies.wav',
  'bosshit'     => 'bosshit.wav',
  'bossspit'    => 'bossspit.wav',
  'laser'       => 'laser.wav',
  'bomb'        => 'bomb.wav',
);

# Intermission : 2E 62 C2

# Set defaults

$PhysicalScreenWidth = 800;
$PhysicalScreenHeight = 600;
$ScreenWidth =  800;
$ScreenHeight = 600;
$SoundEnabled = 1;
$MusicEnabled = 1;
$FullScreen = 1;
$UnicodeMode = 0;
$Difficulty = 1;
$DifficultySetting = 1;
$TutorialPlayed = 0;
$FpsIndicator = 0;
$TimePrecisePlayback = 0;
$MovieMode = 0;
$Cheat = 0;
$Language = '';
do 'config.pl'  or die $! . $@;
do 'player.pl'  or die $! . $@;
&InitPlayers();
&LoadConfig();
do 'translate.pl'  or die $! . $@;
&LoadLanguage();
@DifficultySettingNames = ( T('Easy'), T('Medium'), T('Hard'), T('Insane') );

do 'Texture.pm'  or die $! . $@;
do 'GlFont.pm'  or die $! . $@;
do 'graphics.pl'  or die $! . $@;
do 'sprite.pl'  or die $! . $@;
do 'sound.pl'  or die $!. $@;
do 'gameobject.pl'  or die $! . $@;
do 'boss.pl'  or die $! . $@;
do 'background.pl'  or die $! . $@;
do 'spawner.pl'  or die $! . $@;
do 'game.pl'  or die $! . $@;
do 'level.pl'  or die $! . $@;
do 'event.pl'  or die $! . $@;
do 'menu.pl'   or die $! . $@;
do 'recorder.pl'  or die $! . $@;
do 'tutorial.pl'  or die $! . $@;



##########################################################################
package main;
##########################################################################



##########################################################################
# MAIN PROGRAM STARTS HERE
##########################################################################

sub Initialize {
  srand(time);
  eval { SDL::Init(SDL_INIT_EVERYTHING() | SDL_INIT_NOPARACHUTE()); };
  eval { SDL::Init(SDL::INIT_EVERYTHING() | SDL::INIT_NOPARACHUTE()); }  if $@;  # This is a workaround for SDL_perl 1.2.20
  die "Unable to initialize SDL: $@" if $@;

  if ($MovieMode) {
    do 'movie.pl'  or die $! . $@;
  }
  my $sdlFlags;
  ($PhysicalScreenWidth, $PhysicalScreenHeight) = &FindVideoMode();

  &Joystick::InitJoystick();
  $App = new SDL::App
    -flags => $sdlFlags,
    -title => "Vacuum Magic $Version",
    -icon => "$DataDir/icon.png",
    -width => $PhysicalScreenWidth,
    -height => $PhysicalScreenHeight,
    -fullscreen => $FullScreen,
    -opengl => 1,
    -double_buffer => 1,
    -resizeable => 1,
  ;
  # OK, so certain video cards need some time to get used to being fullscreen and all.
  # I have no idea why. If I start issuing OpenGL commands too quickly, the user gets
  # a nice, black screen, and it stays that way too. It's not always reproducible and
  # I have no way of tracking down this bug.
  # Fucking nasty workaround time:
  sleep(3)  if $FullScreen;
  
  &SDL::ShowCursor(0);
  &InitGL();
  
  glClear(GL_COLOR_BUFFER_BIT);
  $::App->sync();
  glClear(GL_COLOR_BUFFER_BIT);
  &LoadSurfaces();
  &LoadSounds();
  &InitSprites();
  &FpsIndicator::Init();
  &BonusText::CreateBonusTexture();
  $MenuFont = $ScoreFont;
}

sub MainLoop {
  &SetBackground(9);
  &SetMusic('Menu');
  $::MenuFont = $::BrandyFont;
  my $menuResult = &DoMenu();
  $Game->Exit()  unless $menuResult;
  $Game->Fade();
  &ExecuteGame($menuResult);
  $Game->Fade();
}

sub ExecuteGame {
  my ($levelSet) = @_;
  
  &InitLevelFactory($levelSet);
  &StartRecorder();
  $::MenuFont = $::ScoreFont;
  $Game = new NormalGame;
  $Game->Run();
  &StopRecorder();
  &SaveRecord('record.txt');
}

sub ShowErrorMessage {
  my ($message) = @_;
  
  eval("SDL::Quit"); warn $@ if $@;
  $message = "Vacuum Magic $::Version died:\n$message";
  if (&IsMicrosoftWindows()) {
    eval( '
      use Win32;
      Win32::MsgBox($message, MB_ICONEXCLAMATION, "Vacuum Magic error");
    ' );
    return;
  } elsif ($ENV{'DISPLAY'}) {
    $message =~ s/\"/\\"/g;
    my @tryCommands = (
      "kdialog --msgbox \"$message\"",
      "gmessage -center \"$message\"",
      "xmessage -center \"$message\"",
    );
    foreach (@tryCommands) {
      `$_`;
      return if $? == 0;
    }
  }
}

sub ShowWebPage {
  my ($url) = @_;
  
  eval("SDL::Quit"); warn $@ if $@;
  if (&IsMicrosoftWindows()) {
    my $ws = "$DataDir/website.html";
    $ws =~ s/\//\\\\/g;
    exec 'cmd', '/c', $ws;
    exit;
  } elsif ($ENV{'DISPLAY'}) {
    my @tryCommands = (
      "gnome-open $url",
      "mozilla-firefox $url",
      "firefox $url",
      "mozilla $url",
      "konqueror $url",
      "iceweasel $url",
      "sensible-browser $url",
    );
    foreach (@tryCommands) {
      `$_`;
      return if $? == 0;
    }
  } else {
    print "Visit $url for more info about Vacuum Magic $::Version\n";
  }
}


#
# Program Entry Point
#

eval {
  &Initialize();
  #&ExecuteGame('Bosses/PapaKoules');
  #$NumGuys = 1; &RecordTutorials();
  while (1) { &MainLoop(); }
};
if ($@) {
  my $errorMessage = $@;
  &ShowErrorMessage($errorMessage)  if &IsMicrosoftWindows();
  die $errorMessage;
}
