/*************************************************************************** 
  alienBlaster 
  Copyright (C) 2004 
  Paul Grathwohl, Arne Hormann, Daniel Kuehn, Soenke Schwardt

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

  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.
***************************************************************************/
using namespace std;

#include "racer.h"
#include "surfaceDB.h"
#include "shot.h"
#include "shots.h"
#include "items.h"
#include "item.h"
#include "font.h"
#include "boundingBox.h"
#include "enemys.h"
#include "enemy.h"
#include "mixer.h"
#include "global.h"
#include "banners.h"
#include "shieldGlow.h"

Racer::Racer( string fnSprite, int whichPlayer, Vector2D startpos , int newShipType) {

  spriteRacerBase = surfaceDB.loadSurface( fnSprite );
  drawRectBase.w = spriteRacerBase->w;
  drawRectBase.h = spriteRacerBase->h;
  playerNr = whichPlayer;
  pos = startpos;
  shipType = newShipType;

  // init weaponary and specials
  timeLastShotPrimary   = SDL_GetTicks();
  timeLastShotSecondary = SDL_GetTicks();
  lastDumbfireWasLeft = false;
  timeLastHeatseekerUsed = SDL_GetTicks();
  timeLastNukeUsed = SDL_GetTicks();
  lastHeatseekerWasLeft = false;
  deflectorActive = false;
  deflectorTimeLeft = -1;

  for ( int i = 0; i < NR_SECONDARY_WEAPONS; i++ ) {
    secondaryWeaponsAvailability[ i ] = false;
  }
  for ( int i = 0; i < NR_SPECIALS; i++ ) {
    specialsAvailability[ i ] = 0;
  }
  specialsAvailability[ SPECIAL_NONE ] = 1; // this one is always present

  // assign values dependant on shiptype
  switch (shipType) {
    case LIGHT_FIGHTER:
    {
      maxShield = LIGHT_FIGHTER_MAX_SHIELD;
      maxDamage = LIGHT_FIGHTER_MAX_DAMAGE;
      shield = maxShield;
      damage = maxDamage;
      maxVel = LIGHT_FIGHTER_VEL_MAX;
      spriteShadow = surfaceDB.loadSurface( FN_LIGHT_FIGHTER_SHADOW, true );
      shieldGlow = new ShieldGlow( LIGHT_FIGHTER );
      if ( playerNr == 0 ) {
	spriteFighterIcon = surfaceDB.loadSurface( FN_LIGHT_FIGHTER_1_SMALL );
      } else {
	spriteFighterIcon = surfaceDB.loadSurface( FN_LIGHT_FIGHTER_2_SMALL );
      }
      if ( arcadeGame ) {
	secondaryWeaponsAvailability[ SHOT_DUMBFIRE_DOUBLE-100 ] = true;
	secondaryWeaponsAvailability[ SHOT_KICK_ASS_ROCKET-100 ] = true;
	secondaryWeaponsAvailability[ SHOT_HELLFIRE-100 ] = true;
	secondaryWeaponsAvailability[ SHOT_MACHINE_GUN-100 ] = true;
	secondaryWeaponsAvailability[ SHOT_ENERGY_BEAM-100 ] = true;
	primaryShotType = SHOT_TRIPLE;
	activeSecondary = SHOT_HELLFIRE;
	activeSpecial = SPECIAL_NUKE; switchSpecials(); // init specials
      } else {
	secondaryWeaponsAvailability[ 0 ] = true; // Dumbfire is always available
	primaryShotType = SHOT_NORMAL;
	activeSecondary = SHOT_DUMBFIRE;
	activeSpecial = SPECIAL_NUKE; switchSpecials(); // init specials
      }
      break;
    }  
    case HEAVY_FIGHTER:
    {  
      maxShield = HEAVY_FIGHTER_MAX_SHIELD;
      maxDamage = HEAVY_FIGHTER_MAX_DAMAGE;
      shield = maxShield;
      damage = maxDamage;
      maxVel = HEAVY_FIGHTER_VEL_MAX;
      spriteShadow = surfaceDB.loadSurface( FN_HEAVY_FIGHTER_SHADOW, true );
      shieldGlow = new ShieldGlow( HEAVY_FIGHTER );
      if ( playerNr == 0 ) {
	spriteFighterIcon = surfaceDB.loadSurface( FN_HEAVY_FIGHTER_1_SMALL );
      } else {
	spriteFighterIcon = surfaceDB.loadSurface( FN_HEAVY_FIGHTER_2_SMALL );
      }
      if ( arcadeGame ) {
	secondaryWeaponsAvailability[ SHOT_HF_DUMBFIRE_DOUBLE-100 ] = true;
	secondaryWeaponsAvailability[ SHOT_HF_KICK_ASS_ROCKET-100 ] = true;
	secondaryWeaponsAvailability[ SHOT_HF_LASER-100 ] = true;
	primaryShotType = SHOT_HF_QUINTO;
	activeSecondary = SHOT_HF_KICK_ASS_ROCKET;
	activeSpecial = SPECIAL_NUKE; switchSpecials(); // init specials
      } else {
	// Dumbfire is always available
	secondaryWeaponsAvailability[ SHOT_HF_DUMBFIRE-100 ] = true; 
	specialsAvailability[ SPECIAL_DEFLECTOR ] = 1;
	primaryShotType = SHOT_HF_NORMAL;
	activeSecondary = SHOT_HF_DUMBFIRE;
	specialsAvailability[ SPECIAL_NUKE ] = 1;
	activeSpecial = SPECIAL_NUKE; switchSpecials(); // init specials
      }
      break;
    }  
  }

  spriteHPStat = surfaceDB.loadSurface( FN_HITPOINTS_STAT );
  pixPerHP = spriteHPStat->w / (maxDamage + maxShield);

  shieldDamageEndTime = 0;
  shieldDamageActive = false;

  if ( whichPlayer == 0 ) {
    font = new Font( FN_FONT_NUMBERS_LEFT );
  } else {
    font = new Font( FN_FONT_NUMBERS_RIGHT );
  } 
  fontSize = font->getCharWidth();

  sndShotPrimary = Mixer::mixer().loadSample( FN_SOUND_SHOT_PRIMARY );
  sndShotSecondary = Mixer::mixer().loadSample( FN_SOUND_SHOT_SECONDARY );

  spriteSpecials = surfaceDB.loadSurface( FN_ICONS_SPECIALS );
  spriteSecondaryWeapons = surfaceDB.loadSurface( FN_ICONS_SECONDARY_WEAPONS );

  spriteDeflector = surfaceDB.loadSurface( FN_HEAVY_FIGHTER_DEFLECTOR, true );
  drawRectDeflector.w = spriteDeflector->w;
  drawRectDeflector.h = spriteDeflector->h;

  boundaryRect = 
    new RectangleGeo(Vector2D(spriteRacerBase->w/(2*RACER_IMAGE_CNT), spriteRacerBase->h/2),
		     Vector2D(320-spriteRacerBase->w/(2*RACER_IMAGE_CNT), 200-spriteRacerBase->h/2));

  boundingBox = new BoundingBox( lroundf(pos.getX() - (spriteRacerBase->w / RACER_IMAGE_CNT) * 0.45),
				 lroundf(pos.getY() - spriteRacerBase->h * 0.45),
				 lroundf((spriteRacerBase->w / RACER_IMAGE_CNT) * 0.9),
				 lroundf(spriteRacerBase->h * 0.9) );
  
  shipAngle = -90;
  vel = Vector2D(0,0);
  thrust = false;
  backwards = false;
  left = false;
  right = false;
  fireWeapons = false;
  useSpecial = false;
 
  points = 0;

  timeLastMove = SDL_GetTicks();
}


Racer::~Racer() {
  delete boundaryRect;
  delete boundingBox;
  if (font) delete font;
  delete shieldGlow;
}


Vector2D Racer::move( int dT ) {
  // TODO: move this to a correct place
  if ( deflectorActive ) {
    deflectorTimeLeft -= dT;
    if (deflectorTimeLeft < 0) deflectorActive = false;
  }

  Vector2D oldPos = pos;

  if ( thrust ) {
    pos += Vector2D( 0, -maxVel ) * dT / 1000.0;
  }
  if ( backwards ) {
    pos += Vector2D( 0, maxVel ) * dT / 1000.0;
  }
  if ( left ) {
    pos += Vector2D( -maxVel, 0 ) * dT / 1000.0;
  } 
  if ( right ) {
    pos += Vector2D( maxVel, 0 ) * dT / 1000.0;
  }
  
  clipWorld();
  
  updateBoundingBox();

  return oldPos;
}


void Racer::clipWorld() {
  int left = lroundf(pos.getX()) - (spriteRacerBase->w / (2*RACER_IMAGE_CNT));
  int top = lroundf(pos.getY()) - (spriteRacerBase->h / 2);
  int right = lroundf(pos.getX()) + (spriteRacerBase->w / (2*RACER_IMAGE_CNT));
  int bottom = lroundf(pos.getY()) + (spriteRacerBase->h / 2);
  if ( left <= 1 ) {
    pos.setX( 1 + spriteRacerBase->w / (2*RACER_IMAGE_CNT) );
  } else if ( right >= SCREEN_WIDTH - 1 ) {
    pos.setX( SCREEN_WIDTH - 2 - spriteRacerBase->w / (2*RACER_IMAGE_CNT));
  }
  if ( top <= 1 ) {
    pos.setY( 1 + spriteRacerBase->h / 2 );
  } else if ( bottom >= SCREEN_HEIGHT - 1 ) {
    pos.setY( SCREEN_HEIGHT - 2 - spriteRacerBase->h / 2 );
  }
}
    

void Racer::drawStats( SdlCompat_AcceleratedSurface *screen ) {
  SDL_Rect srcR, destR;

  int indent = 5;

  if ( playerNr == 1 ) {
    indent = screen->w - 
      ( 7 + 3 * fontSize + (spriteSpecials->w / (NR_SPECIALS-1)) ) -
      ( 5 + spriteSecondaryWeapons->w / NR_SECONDARY_WEAPONS );
  }

  int x = indent;

  // draw Secondary Weapon
  srcR.x = (activeSecondary - SHOT_DUMBFIRE) * spriteSecondaryWeapons->w / NR_SECONDARY_WEAPONS;
  srcR.y = 0;
  srcR.w = spriteSecondaryWeapons->w / NR_SECONDARY_WEAPONS;
  srcR.h = spriteSecondaryWeapons->h;
  destR.x = x;
  destR.y = 25;
  destR.w = spriteSecondaryWeapons->w / NR_SECONDARY_WEAPONS;
  x += destR.w + 5;
  destR.h = spriteSecondaryWeapons->h;
  SDL_BlitSurface( spriteSecondaryWeapons, &srcR, screen, &destR );
  
  // draw active special and amount of it
  if ( activeSpecial != SPECIAL_NONE ) {
    int pixPerSpecial = spriteSpecials->w / (NR_SPECIALS - 1);
    srcR.y = 0;
    srcR.w = pixPerSpecial;
    srcR.h = spriteSpecials->h;
    srcR.x = (activeSpecial - SPECIAL_NUKE) * pixPerSpecial;
    destR.x = x;
    destR.y = 25;
    destR.w = pixPerSpecial;
    x += pixPerSpecial + 2;
    destR.h = spriteSpecials->h;
    SDL_BlitSurface( spriteSpecials, &srcR, screen, &destR );
    int digitCnt = 1;
    int i=1;
    while ( specialsAvailability[ activeSpecial ] >= i*10 ) {
      digitCnt++;
      i *= 10;
    }
    font->drawInt( screen, x, 25,
		   specialsAvailability[ activeSpecial ], digitCnt, 0);
  }

  if ( !onePlayerGame ) {
    // draw Icon
    switch (playerNr) {
    case 0: indent = 10 + spriteHPStat->w; break;
    case 1: indent = screen->w - (20 + spriteHPStat->w + spriteFighterIcon->w); break;
    default: break;
    }
    destR.x = indent;
    destR.w = spriteFighterIcon->w;
    destR.y = 2;
    destR.h = spriteFighterIcon->h;
    SDL_BlitSurface( spriteFighterIcon, 0, screen, &destR );
  }

  // draw shields and damage
  switch (playerNr) {
  case 0: indent = 5; break;
  case 1: indent = screen->w - (15 + spriteHPStat->w); break;
  default: break;
  }

  // draw damage
  srcR.x = 0;
  srcR.y = 0;
  srcR.w = lroundf(damage * pixPerHP);
  srcR.h = spriteHPStat->h;
  destR.x = indent;
  destR.w = srcR.w;
  destR.y = 5;
  destR.h = spriteHPStat->h;
  SDL_BlitSurface( spriteHPStat, &srcR, screen, &destR );
  // draw shield
  destR.x = indent + srcR.w;
  srcR.x = spriteHPStat->w / 2;
  srcR.w = lroundf(shield * pixPerHP);
  destR.w = srcR.w;
  destR.h = spriteHPStat->h;
  SDL_BlitSurface( spriteHPStat, &srcR, screen, &destR );
}


void Racer::drawShadow( SdlCompat_AcceleratedSurface *screen ) {
  SDL_Rect destR;
  destR.x = lroundf(pos.getX()) - (spriteShadow->w / 2) - 10;
  destR.y = lroundf(pos.getY()) - (spriteShadow->h / 2) + 10;
  destR.w = spriteShadow->w;
  destR.h = spriteShadow->h;
  SDL_BlitSurface( spriteShadow, 0, screen, &destR );
}


void Racer::drawRacer( SdlCompat_AcceleratedSurface *screen ) {

  SDL_Rect srcR;
  SDL_Rect destR;

  destR.x = lroundf(pos.getX()) - (spriteRacerBase->w / (2*RACER_IMAGE_CNT));
  destR.y = lroundf(pos.getY()) - (spriteRacerBase->h / 2);
  destR.w = spriteRacerBase->w / RACER_IMAGE_CNT;
  destR.h = spriteRacerBase->h;
  
  int idx = 0; // normal pos
  if ( !left && !right && !thrust && !backwards ) idx = 0; //stop
  else if ( left && !right && !thrust && !backwards ) idx = 1; // left
  else if ( !left && right && !thrust && !backwards ) idx = 2; // right
  else if ( !left && !right && thrust && !backwards ) idx = 3; // forward
  else if ( !left && !right && !thrust && backwards ) idx = 4; // backward
  else if ( left && !right && thrust && !backwards ) idx = 5; // left-forward
  else if ( !left && right && thrust && !backwards ) idx = 6; // right-forward
  else if ( left && !right && !thrust && backwards ) idx = 7; // left-backward
  else if ( !left && right && !thrust && backwards ) idx = 8; // right-backward
  srcR.x = idx * (spriteRacerBase->w / RACER_IMAGE_CNT);
  srcR.y = 0;
  srcR.w = (spriteRacerBase->w / RACER_IMAGE_CNT);
  srcR.h = spriteRacerBase->h;
  
  SDL_BlitSurface( spriteRacerBase, &srcR, screen, &destR );

  if ( shieldDamageActive ) {
    int shieldGlowTimeLeft = shieldDamageEndTime - SDL_GetTicks();
    if ( shieldGlowTimeLeft < 0 ) {
      shieldDamageActive = false;
    } else {
      shieldGlow->draw( screen, pos, RACER_SHIELD_DAMAGE_LIFETIME - shieldGlowTimeLeft );
    }
  }

  if ( deflectorActive ) {
    drawRectDeflector.x = lroundf(pos.getX()) - spriteDeflector->w / 2;
    drawRectDeflector.y = lroundf(pos.getY()) - spriteDeflector->h / 2;
    SDL_BlitSurface( spriteDeflector, 0, screen, &drawRectDeflector );
  }
}


void Racer::switchSecondary() {
  int newActive = (activeSecondary-100 + 1) % NR_SECONDARY_WEAPONS;
  while ( newActive != activeSecondary-100 &&
	  secondaryWeaponsAvailability[ newActive ] == false ) {
    newActive = (newActive + 1) % NR_SECONDARY_WEAPONS;
  }
  activeSecondary = (ShotTypes)(newActive+100);
  
  if ( activeSecondary == SHOT_DUMBFIRE && 
       secondaryWeaponsAvailability[ SHOT_DUMBFIRE_DOUBLE - 100 ] ) {
    activeSecondary = SHOT_DUMBFIRE_DOUBLE;
  }
  // same for heavy-fighter
  if ( activeSecondary == SHOT_HF_DUMBFIRE && 
       secondaryWeaponsAvailability[ SHOT_HF_DUMBFIRE_DOUBLE - 100 ] ) {
    activeSecondary = SHOT_HF_DUMBFIRE_DOUBLE;
  }

  // TODO: ugly workaround...
  if ( activeSecondary == SHOT_KICK_ASS_ROCKET ||
       activeSecondary == SHOT_HF_KICK_ASS_ROCKET ) {
    timeLastShotSecondary -= RACER_COOLDOWN_KICK_ASS_ROCKET / 2;
  }
}

void Racer::switchSpecials() {
  int newActive = activeSpecial + 1;
  if ( newActive == NR_SPECIALS ) newActive = 1;
  for(int i=0; i < NR_SPECIALS-1; ++i) {
    // Only Sönke knows what the following does ;-)
    if (specialsAvailability[ ((activeSpecial + i) % (NR_SPECIALS-1))+1 ] != 0) {
      activeSpecial = (SpecialTypes)(((activeSpecial + i) % (NR_SPECIALS-1))+1);
      return;
    }
  }
  activeSpecial = SPECIAL_NONE;
}


void Racer::shootPrimary() {
  if ( fireWeapons ) {

    switch ( primaryShotType ) {
    case SHOT_NORMAL:
      {
	if ( (SDL_GetTicks() - RACER_COOLDOWN_SHOT_NORMAL) > timeLastShotPrimary ) {
	  Shot *shot = 
	    new Shot( SHOT_NORMAL, playerNr,
		      pos + Vector2D(0, -spriteRacerBase->h / 2),
		      -90 + (rand() % SPREAD_ANGLE_SHOT_NORMAL) -
		      SPREAD_ANGLE_SHOT_NORMAL / 2 );
	  shots->addShot( shot );
	  Mixer::mixer().playSample( sndShotPrimary, 0 );
	  timeLastShotPrimary = SDL_GetTicks();
	}
	break;
      }
    case SHOT_NORMAL_HEAVY:
      {
	if ( SDL_GetTicks() - RACER_COOLDOWN_SHOT_NORMAL_HEAVY > timeLastShotPrimary ) {
	  Shot *shot = 
	    new Shot( SHOT_NORMAL_HEAVY, playerNr,
		      pos + Vector2D(0, -spriteRacerBase->h / 2), -90 );
	  shots->addShot( shot );
	  Mixer::mixer().playSample( sndShotPrimary, 0 );
	  timeLastShotPrimary = SDL_GetTicks();
	}
	break;
      }
    case SHOT_DOUBLE:
      {
	if ( SDL_GetTicks() - RACER_COOLDOWN_SHOT_DOUBLE > timeLastShotPrimary ) {
	  Shot *shot1 = 
	    new Shot( SHOT_DOUBLE, playerNr, pos + Vector2D(-8,-2), -95 );
	  Shot *shot2 = 
	    new Shot( SHOT_DOUBLE, playerNr, pos + Vector2D(8,-2), -85 );
	  shots->addShot(shot1);
	  shots->addShot(shot2);
	  Mixer::mixer().playSample( sndShotPrimary, 0 );
	  timeLastShotPrimary = SDL_GetTicks();
	}
	break;
      }
    case SHOT_DOUBLE_HEAVY:
      {
	if ( SDL_GetTicks() - RACER_COOLDOWN_SHOT_DOUBLE_HEAVY > timeLastShotPrimary ) {
	  Shot *shot1 = 
	    new Shot( SHOT_DOUBLE_HEAVY, playerNr, pos + Vector2D(-8,-2), -95 );
	  Shot *shot2 = 
	    new Shot( SHOT_DOUBLE_HEAVY, playerNr, pos + Vector2D(8,-2), -85 );
	  shots->addShot(shot1);
	  shots->addShot(shot2);
	  Mixer::mixer().playSample( sndShotPrimary, 0 );
	  timeLastShotPrimary = SDL_GetTicks();
	}
	break;
      }
    case SHOT_TRIPLE:
      {
	if ( SDL_GetTicks() - RACER_COOLDOWN_SHOT_TRIPLE > timeLastShotPrimary ) {
	  Shot *shot1 = 
	    new Shot( SHOT_TRIPLE, playerNr, pos + Vector2D(-8, -2), -100 );
	  Shot *shot2 = 
	    new Shot( SHOT_TRIPLE, playerNr, pos + Vector2D(0, -spriteRacerBase->h / 2), -90 );
	  Shot *shot3 = 
	    new Shot( SHOT_TRIPLE, playerNr, pos + Vector2D(8, -2), -80 );
	  shots->addShot(shot1);
	  shots->addShot(shot2);
	  shots->addShot(shot3);
	  Mixer::mixer().playSample( sndShotPrimary, 0 );
	  timeLastShotPrimary = SDL_GetTicks();
	}
	break;
      }
      
      // HEAVY FIGHTER SHOTS
    case SHOT_HF_NORMAL:
      {
	if ( (SDL_GetTicks() - RACER_COOLDOWN_SHOT_HF_NORMAL) > timeLastShotPrimary ) {
	  Shot *shot = 
	    new Shot( SHOT_HF_NORMAL, playerNr,
		      pos + Vector2D(0, -spriteRacerBase->h / 2), -90 );
	  shots->addShot( shot );
	  Mixer::mixer().playSample( sndShotPrimary, 0 );
	  timeLastShotPrimary = SDL_GetTicks();
	}
	break;
      }
    case SHOT_HF_DOUBLE:
      {
	if ( (SDL_GetTicks() - RACER_COOLDOWN_SHOT_HF_DOUBLE) > timeLastShotPrimary ) {
	  Shot *shot = new Shot( SHOT_HF_DOUBLE, playerNr, pos + Vector2D(-12, -12), -90 );
	  shots->addShot( shot );
	  shot = new Shot( SHOT_HF_DOUBLE, playerNr, pos + Vector2D(12, -12), -90 );
	  shots->addShot( shot );
	  Mixer::mixer().playSample( sndShotPrimary, 0 );
	  timeLastShotPrimary = SDL_GetTicks();
	}
	break;
      }
    case SHOT_HF_TRIPLE:
      {
	if ( (SDL_GetTicks() - RACER_COOLDOWN_SHOT_HF_TRIPLE) > timeLastShotPrimary ) {
	  Shot *shot = new Shot( SHOT_HF_TRIPLE, playerNr, pos + Vector2D(-12, -12), -103 );
	  shots->addShot( shot );
	  shot = new Shot( SHOT_HF_TRIPLE, playerNr, 
			   pos + Vector2D(0, -spriteRacerBase->h / 2), -90 );
	  shots->addShot( shot );
	  shot = new Shot( SHOT_HF_TRIPLE, playerNr, pos + Vector2D(12, -12), -77 );
	  shots->addShot( shot );
	  Mixer::mixer().playSample( sndShotPrimary, 0 );
	  timeLastShotPrimary = SDL_GetTicks();
	}
	break;
      }
    case SHOT_HF_QUATTRO:
      {
	if ( (SDL_GetTicks() - RACER_COOLDOWN_SHOT_HF_QUATTRO) > timeLastShotPrimary ) {
	  Shot *shot = new Shot( SHOT_HF_QUATTRO, playerNr, pos + Vector2D(-30, -15), -100 );
	  shots->addShot( shot );
	  shot = new Shot( SHOT_HF_QUATTRO, playerNr, pos + Vector2D(-12, -12), -90 );
	  shots->addShot( shot );
	  shot = new Shot( SHOT_HF_QUATTRO, playerNr, pos + Vector2D(12, -12), -90 );
	  shots->addShot( shot );
	  shot = new Shot( SHOT_HF_QUATTRO, playerNr, pos + Vector2D(30, -15), -80 );
	  shots->addShot( shot );
	  Mixer::mixer().playSample( sndShotPrimary, 0 );
	  timeLastShotPrimary = SDL_GetTicks();
	}
	break;
      }
    case SHOT_HF_QUINTO:
      {
	if ( (SDL_GetTicks() - RACER_COOLDOWN_SHOT_HF_QUINTO) > timeLastShotPrimary ) {
	  Shot *shot = new Shot( SHOT_HF_QUINTO, playerNr, pos + Vector2D(-30, -15), -110 );
	  shots->addShot( shot );
	  shot = new Shot( SHOT_HF_QUINTO, playerNr, pos + Vector2D(-12, -12), -100 );
	  shots->addShot( shot );
	  shot = new Shot( SHOT_HF_QUINTO, playerNr, 
			   pos + Vector2D(0, -spriteRacerBase->h / 2), -90 );
	  shots->addShot( shot );
	  shot = new Shot( SHOT_HF_QUINTO, playerNr, pos + Vector2D(12, -12), -80 );
	  shots->addShot( shot );
	  shot = new Shot( SHOT_HF_QUINTO, playerNr, pos + Vector2D(30, -15), -70 );
	  shots->addShot( shot );
	  Mixer::mixer().playSample( sndShotPrimary, 0 );
	  timeLastShotPrimary = SDL_GetTicks();
	}
	break;
      }

    default: 
      {
	cout << "Racer::shootPrimary: unexpected primaryShotType: " 
	     << primaryShotType << endl;
	break;
      }
    }
  }
}


void Racer::shootSecondary() {
  if ( fireWeapons ) {

    switch ( activeSecondary ) {

    case SHOT_DUMBFIRE:
      {
	if ( SDL_GetTicks() - RACER_COOLDOWN_DUMBFIRE > timeLastShotSecondary ) {
	  Shot *shot = 
	    new Shot( SHOT_DUMBFIRE, playerNr, pos + Vector2D(0, -5), -90 );
	  shots->addShot(shot);
	  Mixer::mixer().playSample( sndShotSecondary, 0 );
	  timeLastShotSecondary = SDL_GetTicks();
	}
	break;
      }
    case SHOT_DUMBFIRE_DOUBLE:
      {
	if ( SDL_GetTicks() - RACER_COOLDOWN_DUMBFIRE_DOUBLE > timeLastShotSecondary ) {
	  Shot *shot;
	  if ( lastDumbfireWasLeft ) {
	    shot = new Shot( SHOT_DUMBFIRE_DOUBLE, playerNr, pos + Vector2D(15,-3), -90 );
	  } else {
	    shot = new Shot( SHOT_DUMBFIRE_DOUBLE, playerNr, pos + Vector2D(-15,-3), -90 );
	  }
	  lastDumbfireWasLeft = !lastDumbfireWasLeft;
	  shots->addShot(shot);
	  Mixer::mixer().playSample( sndShotSecondary, 0 );
	  timeLastShotSecondary = SDL_GetTicks();
	}
	break;
      }
    case SHOT_KICK_ASS_ROCKET:
      {
	if ( SDL_GetTicks() - RACER_COOLDOWN_KICK_ASS_ROCKET > timeLastShotSecondary ) {
	  Shot *shot = 
	    new Shot( SHOT_KICK_ASS_ROCKET, playerNr, pos, -90 );
	  shots->addShot(shot);
	  Mixer::mixer().playSample( sndShotSecondary, 0 );
	  timeLastShotSecondary = SDL_GetTicks();
	}
	break;
      }
    case SHOT_HELLFIRE:
      {
	if ( SDL_GetTicks() - RACER_COOLDOWN_HELLFIRE > timeLastShotSecondary ) {
	  Shot *shot1 = 
	    new Shot( SHOT_HELLFIRE, playerNr, pos + Vector2D(-15, -3), -180 );
	  Shot *shot2 = 
	    new Shot( SHOT_HELLFIRE, playerNr, pos + Vector2D(15, -3), 0 );
	  shots->addShot(shot1);
	  shots->addShot(shot2);
	  Mixer::mixer().playSample( sndShotSecondary, 0 );
	  timeLastShotSecondary = SDL_GetTicks();
	}
	break;
      }
    case SHOT_MACHINE_GUN:
      {
	if ( SDL_GetTicks() - RACER_COOLDOWN_MACHINE_GUN > timeLastShotSecondary ) {

	  int idxNearestEnemy1 = 0;
	  float distFarestY1 = -1;
	  int idxNearestEnemy2 = 0;
	  float distFarestY2 = -1;
	  float angle1 = -90;
	  float angle2 = -90;
	  float maxDistY = pos.getY() + 40; // only enemys in or already in the screen
	  for ( unsigned int i = 0; i < enemys->getNrEnemys(); i++ ) {
	    // angle of the vector from the racer position to the enemy
	    float angleEnemy = (enemys->getEnemy(i)->getPos() - pos).getDirection();
	    if ( -90 <= angleEnemy && angleEnemy < -60 ) {
	      float distY = pos.getY() - enemys->getEnemy(i)->getPos().getY();
	      if ( distFarestY1 < distY && distY < maxDistY ) {
		distFarestY1 = distY;
		idxNearestEnemy1 = i;
		angle2 = angleEnemy + 5;
	      }
	    } else {
	      if ( -120 < angleEnemy && angleEnemy < -90 ) {
		float distY = pos.getY() - enemys->getEnemy(i)->getPos().getY();
		if ( distFarestY2 < distY && distY < maxDistY ) {
		  distFarestY2 = distY;
		  idxNearestEnemy2 = i;
		  angle1 = angleEnemy - 5;
		}
	      }
	    }
	  }
	  
	  Shot *shot1 = 
	    new Shot( SHOT_MACHINE_GUN, playerNr, 
		      pos + Vector2D(-3, -spriteRacerBase->h/2), angle1 );
	  Shot *shot2 = 
	    new Shot( SHOT_MACHINE_GUN, playerNr, 
		      pos + Vector2D(+3, -spriteRacerBase->h/2), angle2 );
	  shots->addShot(shot1);
	  shots->addShot(shot2);
	  Mixer::mixer().playSample( sndShotSecondary, 0 );
	  timeLastShotSecondary = SDL_GetTicks();
	}
	break;
      }
    case SHOT_ENERGY_BEAM:
      {
	if ( SDL_GetTicks() - RACER_COOLDOWN_ENERGY_BEAM > timeLastShotSecondary ) {
	  Shot *shot = 
	    new Shot( SHOT_ENERGY_BEAM, playerNr, pos + Vector2D(-1, -10), -90 );
	  shots->addShot(shot);
	  Mixer::mixer().playSample( sndShotSecondary, 0 );
	  timeLastShotSecondary = SDL_GetTicks();
	}
	break;
      }
      
      // HEAVY FIGHTER SHOTS
    case SHOT_HF_DUMBFIRE:
      {
	if ( SDL_GetTicks() - RACER_COOLDOWN_HF_DUMBFIRE > timeLastShotSecondary ) {
	  Shot *shot = 
	    new Shot( SHOT_HF_DUMBFIRE, playerNr, pos + Vector2D(-30, -5), -90 );
	  shots->addShot(shot);
	  shot = 
	    new Shot( SHOT_HF_DUMBFIRE, playerNr, pos + Vector2D(30, -5), -90 );
	  shots->addShot(shot);
	  Mixer::mixer().playSample( sndShotSecondary, 0 );
	  timeLastShotSecondary = SDL_GetTicks();
	}
	break;
      }
    case SHOT_HF_DUMBFIRE_DOUBLE:
      {
	if ( SDL_GetTicks() - RACER_COOLDOWN_HF_DUMBFIRE_DOUBLE > timeLastShotSecondary ) {
	  Shot *shot = 
	    new Shot( SHOT_HF_DUMBFIRE_DOUBLE, playerNr, pos + Vector2D(-30, -5), -90 );
	  shots->addShot(shot);
	  shot = 
	    new Shot( SHOT_HF_DUMBFIRE_DOUBLE, playerNr, pos + Vector2D(30, -5), -90 );
	  shots->addShot(shot);
	  Mixer::mixer().playSample( sndShotSecondary, 0 );
	  timeLastShotSecondary = SDL_GetTicks();
	}
	break;
      }
    case SHOT_HF_KICK_ASS_ROCKET:
      {
	if ( SDL_GetTicks() - RACER_COOLDOWN_HF_KICK_ASS_ROCKET > timeLastShotSecondary ) {
	  Shot *shot1 = 
	    new Shot( SHOT_HF_KICK_ASS_ROCKET, playerNr, pos + Vector2D(-30,0), -90 );
	  shots->addShot(shot1);
	  Shot *shot2 = 
	    new Shot( SHOT_HF_KICK_ASS_ROCKET, playerNr, pos + Vector2D(30,0), -90 );
	  shots->addShot(shot2);
	  Mixer::mixer().playSample( sndShotSecondary, 0 );
	  timeLastShotSecondary = SDL_GetTicks();
	}
	break;
      }
    case SHOT_HF_LASER:
      {
	if ( SDL_GetTicks() - RACER_COOLDOWN_HF_LASER > timeLastShotSecondary ) {
	  Shot *shot1 = 
	    new Shot( SHOT_HF_LASER, playerNr, pos + Vector2D(-30,-10), -90 );
	  shots->addShot(shot1);
	  Shot *shot2 = 
	    new Shot( SHOT_HF_LASER, playerNr, pos + Vector2D(30,-10), -90 );
	  shots->addShot(shot2);
	  Mixer::mixer().playSample( sndShotSecondary, 0 );
	  timeLastShotSecondary = SDL_GetTicks();
	}
	break;
      }

    default: 
      {
	cout << "Racer::shootSecondary: unexpected activeSecondary: " 
	     << activeSecondary << endl;
	break;
      }      
    }

  }
}


void Racer::shootSpecial() {
  if ( useSpecial ) {

    switch ( activeSpecial ) {

    case SPECIAL_HEATSEEKER:
      {
	if ( SDL_GetTicks() - RACER_COOLDOWN_SPECIAL_HEATSEEKER > timeLastHeatseekerUsed &&
	     specialsAvailability[ SPECIAL_HEATSEEKER ] > 0 ) {
	  Shot *shot;
	  if ( lastHeatseekerWasLeft ) {
	    shot = new Shot( SPECIAL_SHOT_HEATSEEKER, playerNr, pos + Vector2D(15,-3), -90 );
	  } else {
	    shot = new Shot( SPECIAL_SHOT_HEATSEEKER, playerNr, pos + Vector2D(-15,-3), -90 );
	  }
	  lastHeatseekerWasLeft = !lastHeatseekerWasLeft;
	  shots->addShot(shot);
	  Mixer::mixer().playSample( sndShotSecondary, 0 );
	  timeLastHeatseekerUsed = SDL_GetTicks();
	  specialsAvailability[ SPECIAL_HEATSEEKER ]--;
	}
	break;
      }
    default: break;
    }
  }
}


bool Racer::collidesWith( const Vector2D &shotPosOld, const Vector2D &shotPosNew ) {
  return boundingBox->overlaps(shotPosOld, shotPosNew);
}

bool Racer::collidesWith( BoundingBox *box ) {
  return boundingBox->overlaps( box );
}

bool Racer::collidesWith( const Circle &circle ) {
  return boundingBox->overlaps( circle );
}

bool Racer::collidesWithAsCircle( const Circle &circle ) {
  return ( (circle.getRadius() + (spriteRacerBase->w / RACER_IMAGE_CNT)/2) > circle.getCenter().distanceTo( pos ) );
}

bool Racer::collidesWithAsCircle( BoundingBox *box ) {
  return ( box->overlaps( Circle( pos, spriteRacerBase->h >> 1 ) ) );
}

BoundingBox *Racer::getBoundingBox() {
  return boundingBox;
}


bool Racer::isDead() {
  return (damage < 0);
}


void Racer::receiveDamage( float amount ) {
  if ( lroundf(shield) >= amount ) {
    shield -= amount;
    shieldDamageActive = true;
    shieldDamageEndTime = SDL_GetTicks() + RACER_SHIELD_DAMAGE_LIFETIME;
    return;
  }
  amount -= shield;
  shield = 0;
  damage -= amount;
}

void Racer::doDamage( ShotTypes shotType ) {

  switch (shotType) {
  case ENEMY_SHOT_NORMAL:
    {
      receiveDamage( DAMAGE_ENEMY_SHOT_NORMAL );
      break;
    }
  case ENEMY_SHOT_TANK_ROCKET:
    {
      receiveDamage( DAMAGE_ENEMY_SHOT_TANK_ROCKET );
      break;
    }
  default: 
    {
      cout << "Racer::doDamage: unexpected shotType: " << shotType << endl;
      break;
    }
  }
}

void Racer::rechargeShield ( int dT ) {
  float amount;
  if ( shipType == LIGHT_FIGHTER ) {
    amount = LIGHT_FIGHTER_SHIELD_RECHARGE * dT / 100000.0;
  } else {
    amount = HEAVY_FIGHTER_SHIELD_RECHARGE * dT / 100000.0;
  }
  if (shield + amount < maxShield) shield += amount;
  else shield = maxShield;
}

void Racer::receivePoints( float amount ) {
  float oldPoints = points;
  points += amount;

  if ( arcadeGame ) {
    // time for a health item?
    for ( int i = 0; i < NR_ARCACE_POINTS_FOR_HEALTH_ITEM; i++ ) {
      if ( oldPoints < ARCADE_POINTS_FOR_HEALTH_ITEM[ i ] &&
	   ARCADE_POINTS_FOR_HEALTH_ITEM[ i ] <= points ) {
	Item *health = new Item( Vector2D( SCREEN_WIDTH / 2, -30 ),
				 Vector2D( 0, 30 ),
				 ITEM_HEALTH );
	items->addItem( health );
	banners->addBanner( BANNER_HEALTH, BANNER_MODE_FLY_FROM_LEFT, BANNER_BONUS_NONE );
	break;
      }
    }
  }

}


Vector2D Racer::setVel( const Vector2D &newVel ) {
  Vector2D temp = vel;
  vel = newVel;
  if ( vel.getLength() >= getActVelMax() ) {
    vel.setLength(getActVelMax());
  }
  return temp;
}


void Racer::setPos( const Vector2D &newPos ) {
  pos = newPos;
  updateBoundingBox();
}


void Racer::updateBoundingBox() {
  boundingBox->moveUpperBound( lroundf(pos.getY() - spriteRacerBase->h * 0.45) );
  boundingBox->moveLeftBound( lroundf(pos.getX() - (spriteRacerBase->w / RACER_IMAGE_CNT) * 0.45) );
}


void Racer::pickUpItems() {
  for ( unsigned int i = 0; i < items->getNrItems(); i++ ) {
    Item *item = items->getItem(i);
    if ( !item->isExpired() && boundingBox->overlaps( item->getBoundingBox() ) ) {
      switch (item->getType()) {
      case ITEM_PRIMARY_UPGRADE: 
	{
	  int newPrimaryShotType = primaryShotType + 1;
	  if ( shipType == LIGHT_FIGHTER && newPrimaryShotType <= SHOT_TRIPLE ) 
	    primaryShotType = (ShotTypes)newPrimaryShotType;
	  else if ( shipType == HEAVY_FIGHTER && newPrimaryShotType <= SHOT_HF_QUINTO )
	    primaryShotType = (ShotTypes)newPrimaryShotType;
	  item->pickedUp();
	  addBannerItemCollected( BANNER_ITEM_PRIMARY_UPGRADE_COLLECTED );
	  break;
	}
      case ITEM_DUMBFIRE_DOUBLE:
	{
	  if ( shipType == LIGHT_FIGHTER ) {
	    secondaryWeaponsAvailability[ SHOT_DUMBFIRE_DOUBLE - 100 ] = true;
	    if (activeSecondary == SHOT_DUMBFIRE ) activeSecondary = SHOT_DUMBFIRE_DOUBLE;
	  } else {
	    secondaryWeaponsAvailability[ SHOT_HF_DUMBFIRE_DOUBLE - 100 ] = true;
	    if (activeSecondary == SHOT_HF_DUMBFIRE ) activeSecondary = SHOT_HF_DUMBFIRE_DOUBLE;
	  }
	  item->pickedUp();
	  addBannerItemCollected( BANNER_ITEM_DUMBFIRE_DOUBLE_COLLECTED );
	  break;
	}
      case ITEM_KICK_ASS_ROCKET:
	{
	  if ( shipType == LIGHT_FIGHTER )
	    secondaryWeaponsAvailability[ SHOT_KICK_ASS_ROCKET - 100 ] = true;
	  else
	    secondaryWeaponsAvailability[ SHOT_HF_KICK_ASS_ROCKET - 100 ] = true;
	  item->pickedUp();
	  addBannerItemCollected( BANNER_ITEM_KICK_ASS_ROCKET_COLLECTED );
	  break;
	}
      case ITEM_HELLFIRE:
	{
	  if ( shipType == LIGHT_FIGHTER ) {
	    secondaryWeaponsAvailability[ SHOT_HELLFIRE - 100 ] = true;
	    item->pickedUp();
	    addBannerItemCollected( BANNER_ITEM_HELLFIRE_COLLECTED );
	  }
	  break;
	}
      case ITEM_MACHINE_GUN:
	{
	  if ( shipType == LIGHT_FIGHTER ) {
	    secondaryWeaponsAvailability[ SHOT_MACHINE_GUN - 100 ] = true;
	    item->pickedUp();
	    addBannerItemCollected( BANNER_ITEM_MACHINE_GUN_COLLECTED );
	  }
	  break;
	}
      case ITEM_HEALTH:
	{
	  if ( arcadeGame ) {
	    repair( ITEM_HEALTH_REPAIR_AMOUNT );
	  } else {
	    if ( shipType == LIGHT_FIGHTER )
	      repair( ITEM_HEALTH_REPAIR_AMOUNT );
	    else repair( ITEM_HEALTH_REPAIR_AMOUNT * ITEM_HEALTH_REPAIR_FACTOR_HEAVY_FIGHTER );
	  }
	  item->pickedUp();
	  addBannerItemCollected( BANNER_ITEM_HEALTH_COLLECTED );
	  break;
	}
      case ITEM_HEATSEEKER:
	{
	  specialsAvailability[ SPECIAL_HEATSEEKER ] += ITEM_HEATSEEKER_AMMO;
	  activeSpecial = SPECIAL_HEATSEEKER;
	  item->pickedUp();
	  addBannerItemCollected( BANNER_ITEM_HEATSEEKER_COLLECTED );
	  break;
	}
      case ITEM_NUKE:
	{
	  specialsAvailability[ SPECIAL_NUKE ] += ITEM_NUKE_AMMO;
	  if ( activeSpecial == SPECIAL_NONE ) {
	    activeSpecial = SPECIAL_NUKE;
	  }
	  item->pickedUp();
	  addBannerItemCollected( BANNER_ITEM_NUKE_COLLECTED );
	  break;
	}
      case ITEM_DEFLECTOR:
	{
	  if (shipType == HEAVY_FIGHTER ) {
	    specialsAvailability[ SPECIAL_DEFLECTOR ] += ITEM_DEFLECTOR_AMMO;
	    if ( activeSpecial == SPECIAL_NONE ) {
	      activeSpecial = SPECIAL_DEFLECTOR;
	    }
	    item->pickedUp();
	    addBannerItemCollected( BANNER_ITEM_DEFLECTOR_COLLECTED );
	  }
	  break;
	}
      case ITEM_ENERGY_BEAM:
	{
	  if ( shipType == LIGHT_FIGHTER ) {
	    secondaryWeaponsAvailability[ SHOT_ENERGY_BEAM - 100 ] = true;
	    item->pickedUp();
	    addBannerItemCollected( BANNER_ITEM_ENERGY_BEAM_COLLECTED );
	  }
	  break;
	}
      case ITEM_LASER:
	{
	  if ( shipType == HEAVY_FIGHTER ) {
	    secondaryWeaponsAvailability[ SHOT_HF_LASER - 100 ] = true;
	    addBannerItemCollected( BANNER_ITEM_LASER_COLLECTED );
	    item->pickedUp();
	  }
	  break;
	}
	  
      default:
	{
	  cout << "pickUpItems: unexpected itemType: " << item->getType() << endl;
	  break;
	}
      }
    }
  }
}


float Racer::getActVelMax() {
  return maxVel;
}


void Racer::repair( float amount ) {
  float possibleDamageAdd = maxDamage - damage;
  if ( possibleDamageAdd >= amount ) {
    damage += amount;
  } else {
    damage += possibleDamageAdd;
    amount -= possibleDamageAdd;
    shield += amount;
  }
  if ( shield > maxShield ) shield = maxShield;
}


void Racer::specialKeyDown() {
  switch ( activeSpecial ) {
  case SPECIAL_NUKE: 
    {
      if ( timeLastNukeUsed + RACER_COOLDOWN_SPECIAL_NUKE < SDL_GetTicks() ) {
	shots->addShot( new Shot( SPECIAL_SHOT_NUKE,
				  playerNr, pos, 
				  (Vector2D( SCREEN_WIDTH / 2.0, 
					     SCREEN_HEIGHT / 2.0 ) - pos).getDirection() ) );
	specialsAvailability[ SPECIAL_NUKE ]--;
	timeLastNukeUsed = SDL_GetTicks();
      }
      break;
    }
  case SPECIAL_DEFLECTOR:
    {
      if ( !deflectorActive ) {
	deflectorActive = true;
	deflectorTimeLeft = ITEM_DEFLECTOR_DURATION;
	specialsAvailability[ SPECIAL_DEFLECTOR ]--;
      }
      break;
    }
  default:
    {
      break;
    }
  }
  if ( specialsAvailability[ activeSpecial ] == 0 ) {
    switchSpecials();
  }
}

void Racer::specialKeyUp() {
  if ( specialsAvailability[ activeSpecial ] == 0 ) {
    switchSpecials();
  }
}

void Racer::handlePlayerEvent( PlayerEvent pEvent, bool keyDown ) {
  switch (pEvent) {
  case PE_UP: if ( keyDown ) thrust = true; else thrust = false; break;
  case PE_DOWN: if ( keyDown ) backwards = true; else backwards = false; break;
  case PE_LEFT: if ( keyDown ) left = true; else left = false; break;
  case PE_RIGHT: if ( keyDown ) right = true; else right = false; break;
  case PE_FIRE_SPECIALS:// PE_FIRE_WEAPONS: 
  if ( keyDown ) fireWeapons = true; else fireWeapons = false; break;
  case PE_CHOOSE_WEAPON_SECONDARY: 
    {
      if ( !keyDown ) { 
	switchSecondary();
      }
      break;
    }
  case PE_CHOOSE_WEAPON_SPECIALS: 
    {
      if ( !keyDown ) {
	switchSpecials();
      }
      break;
    }
  case PE_FIRE_WEAPONS: // PE_FIRE_SPECIALS: 
    {
      if ( keyDown ) {
	useSpecial = true; 
	specialKeyDown();
      } else {
	useSpecial = false; 
	specialKeyUp();
	break;
      }
    }
  default: break;
  }
}


void Racer::addBannerItemCollected( BannerTexts text ) {
  if ( onePlayerGame ) {
    banners->addBanner( text,
			BANNER_MODE_ITEM_COLLECTED_SINGLE_PLAYER,
			BANNER_BONUS_NONE );
  } else {
    if ( playerNr == 0 ) {
      banners->addBanner( text,
			  BANNER_MODE_ITEM_COLLECTED_PLAYER_ONE,
			  BANNER_BONUS_NONE );
    } else {
      banners->addBanner( text,
			  BANNER_MODE_ITEM_COLLECTED_PLAYER_TWO,
			  BANNER_BONUS_NONE );
    }
  }
}

      
      
