#include "KGF/KGF.h"

VObject *background;

int GlobalScore=0;
const int TANK_LIFE=200;
float DifficultyScaling=0;

std::string NewObject="";
float NewObject_Opacity=0.0;


float WhiteFlashOpacity=0;
float NukeFlashOpacity=0;

void CreateSmokeAt(float x, float y, float op=0.9) {
  VObject *s;
  s = AddVObject(new VObject(new AnimatedSprite("Data/Graphics/Smoke"), x, y, 0.2, 0));
  s->AnimateScale(5.0, 200);
  s->SetTint(al_map_rgba_f(1.0, 1.0, 1.0, op));
  s->AnimateTint(1.0, 1.0, 1.0, 0.0, 200);
  s->AnimateRotate(AngleToRad(45), 200);
  s->SetAtFrame(randn(5)-1);
  s->SetLife(200);
  s->SetZ(6);
}

void CreateDustAt(float x, float y, float op=0.9) {
  VObject *s;
  s = AddVObject(new VObject(GetBitmap("Data/Graphics/Dust.png"), x, y, 0.2, 0));
  s->AnimateScale(5.0, 200);
  s->SetTint(al_map_rgba_f(1.0, 1.0, 1.0, op));
  s->AnimateTint(1.0, 1.0, 1.0, 0.0, 200);
  s->AnimateRotate(AngleToRad(45), 200);
  s->SetLife(200);
  s->SetZ(6);
}

void CreatePrintAt(float x, float y, float angle, float sc=1.0) {
  VObject *s;
  s = AddVObject(new VObject(GetBitmap("Data/Graphics/TankPrint.png"), x, y, sc, angle));
  s->SetTint(al_map_rgba_f(1.0, 1.0, 1.0, 1.0));
  s->AnimateTint(1.0, 1.0, 1.0, 0.0, 100);
  s->SetLife(100);
  s->SetZ(-0.5);
}

void CreateLightAt(float x, float y, float sc=3.0) {
  VObject *s;
  s = AddVObject(new VObject(GetBitmap("Data/Graphics/TankShootLight.png"), x, y, sc, 0));
  s->SetTint(al_map_rgba_f(1.0, 1.0, 1.0, 0.3));
  s->AnimateTint(1.0, 1.0, 1.0, 0.0, 50);
  s->SetLife(200);
  s->SetZ(6);
}

bool ShownScore=false;

const float SMOKE_DELAY_TANK=1;
const float PRINT_DELAY_TANK=5;

const float EXTRA_LIMIT=50;


enum MissileType {
  MISSILE,
  BULLET,
  HOMING_MISSILE,
  FLAMETHROWER,
  LASER_GUN,
  ZAP_GUN,
  NUCLEAR_MISSILE,
  HEALTH
};


class Weapon {
  public:
    int ammo;
    enum MissileType type;
    Weapon(int am, enum MissileType tp) : ammo(am), type(tp) {}
};

std::deque<Weapon> Weapons;
int ActualWeapon=0;


void DropAmmoBy(int v) {
  Weapons[ActualWeapon].ammo -= v;
  if (Weapons[ActualWeapon].ammo <= 0) {
    Weapons[ActualWeapon].ammo = 0;
    ActualWeapon=0;
  }
}



class Box {
  protected:
    float x, y, z, angle;
    enum MissileType tp;
    VObject *box;
    float life;
    bool v_e, landed;
  public:
    Box(float tx, float ty) {
      x=tx; y=ty; angle=AngleToRad(randn(360));

      box = AddVObject(new VObject(GetBitmap("Data/Graphics/Box.png"), x, y, 2.0, 0));
      box->SetZ(-0.1);
      box->AnimateScale(0.3, 60);

      GetSound("Data/Sounds/CrateDrop.ogg")->Play();

      int v;
      v = randn(100);
      if ((v >= 1) && (v < 15)) tp = HEALTH;
      if ((v >= 15) && (v < 40)) tp = HOMING_MISSILE;
      if ((v >= 40) && (v < 65)) tp = FLAMETHROWER;
      if ((v >= 65) && (v < 75)) tp = LASER_GUN;
      if ((v >= 75) && (v < 95)) tp = ZAP_GUN;
      if ((v >= 95) && (v <= 100)) tp = NUCLEAR_MISSILE;

      landed = false;

      z = 6;

      v_e=false;

      life = 100;
    }

    void Update() {
      if (life <= 0) return;
      box->SetX(x); box->SetY(y);box->SetZ(z); box->SetAngle(angle+AngleToRad(90));
      if (z > -0.1) {
        z -= DeltaTime * 0.1;
        angle += 0.01;
        if (angle > AngleToRad(360)) angle -= AngleToRad(360);
      }
      if ((z < -0.1) && (!landed)) {
        z = -0.1;
        landed = true;
        GetSound("Data/Sounds/CrateImpact.ogg")->Play();
        float l, a, s;
        s = 6;
        a = AngleToRad(360) / s;
        for (int i=0; i!=s; i+=1) {
          l = a * i;
          float tx, ty;
          tx = x + cos(l) * 35;
          ty = y + sin(l) * 35;
          CreateSmokeAt(tx, ty);
        }

      }
    }

    float GetX() {return x;}
    float GetY() {return y;}
    float Landed() {return landed;}

    void Kill() {
      life = 0;
      box->SetLife(60);
      box->AnimateTint(1,1,1,0, 60);
      box->AnimateScale(0.5, 60);
      v_e=true;
    }

    enum MissileType GetType() {return tp;}


    bool Dead() {
      return (life <= 0);
    }

    ~Box() {
      if ((!v_e) && (box != NULL)) {box->SetLife(0);}
    }
};

std::deque<Box *> Boxes;

void AddBox(Box *m) {
  Boxes.push_back(m);
}


void CleanupBoxes() {
  std::deque<Box *> new_list;
  std::deque<Box *> del_list;
  new_list.clear();
  del_list.clear();
  std::deque<Box *>::iterator it;
  for (it=Boxes.begin(); it!=Boxes.end(); it++) {
    if ((*it)->Dead()) {
      del_list.push_back(*it);
    }
    else {
      new_list.push_back(*it);
    }
  }
  Boxes.clear();
  for (it=new_list.begin(); it!=new_list.end(); it++) {
    Boxes.push_back(*it);
  }
  for (it=del_list.begin(); it!=del_list.end(); it++) {
    delete (*it);
  }
  new_list.clear();
  del_list.clear();
}


void UpdateBoxes() {
  static float NextCleanup=50;

  NextCleanup -= DeltaTime;
  if (NextCleanup < 0) {
    NextCleanup = 60;
    CleanupBoxes();
  }

  for (std::deque<Box *>::iterator it=Boxes.begin(); it!=Boxes.end(); it++) {
    (*it)->Update();
  }
}



class Missile {
  protected:
    float x, y, angle, speed;
    VObject *missile;
    float life;
    enum MissileType type;
    bool v_e;

    void *target;
  public:
    Missile(float tx, float ty, float a, enum MissileType tp=MISSILE, void *t=NULL) {
      x=tx; y=ty; angle=a;
      speed = 16;

      type = tp;
      life = 100;

      switch (type) {
        case MISSILE :
          missile = AddVObject(new VObject(GetBitmap("Data/Graphics/Missile.png"), x, y, 2.0, 0));
          missile->SetMotionBlur(true);
          break;
        case HOMING_MISSILE :
          missile = AddVObject(new VObject(GetBitmap("Data/Graphics/HomingMissile.png"), x, y, 2.0, 0));
          missile->SetMotionBlur(true);
          break;
        case BULLET :
          missile = AddVObject(new VObject(GetBitmap("Data/Graphics/Missile.png"), x, y, 0.5, 0));
          missile->SetMotionBlur(true);
          break;
        case FLAMETHROWER :
          missile = AddVObject(new VObject(GetBitmap("Data/Graphics/Flame" + ToString(randn(3)) + ".png"), x, y, 0.1+(randn(50)/100.0-0.25), AngleToRad(randn(360))));
          missile->AnimateScale(10.0, 200);
          missile->SetTint(al_map_rgba_f(1.0, 1.0, 1.0, 0.6));
          missile->AnimateTint(1.0, 1.0, 1.0, 0.0, 200);
          missile->SetZ(6);
          life = 220;
          speed = 3;
          break;
      }

      missile->SetZ(2);

      target = t;

      v_e=false;
    }

    void Update();


    bool Dead() {
      return (life <= 0);
    }

    ~Missile() {
      if (!v_e) missile->SetLife(0);
    }
    void ExplodeMissile();
};

std::deque<Missile *> missiles;

void AddMissile(Missile *m) {
  missiles.push_back(m);
}


void CleanupMissiles() {
  std::deque<Missile *> new_list;
  std::deque<Missile *> del_list;
  new_list.clear();
  del_list.clear();
  std::deque<Missile *>::iterator it;
  for (it=missiles.begin(); it!=missiles.end(); it++) {
    if ((*it)->Dead()) {
      del_list.push_back(*it);
    }
    else {
      new_list.push_back(*it);
    }
  }
  missiles.clear();
  for (it=new_list.begin(); it!=new_list.end(); it++) {
    missiles.push_back(*it);
  }
  for (it=del_list.begin(); it!=del_list.end(); it++) {
    delete (*it);
  }
  new_list.clear();
  del_list.clear();
}


void UpdateMissiles() {
  static float NextCleanup=50;

  NextCleanup -= DeltaTime;
  if (NextCleanup < 0) {
    NextCleanup = 60;
    CleanupMissiles();
  }

  for (std::deque<Missile *>::iterator it=missiles.begin(); it!=missiles.end(); it++) {
    (*it)->Update();
  }
}



class Jeep {
  protected:
  float x, y, angle, angle_top, speed, max_speed, print_delay, shoot_delay, life, sc;
  VObject *base;
  bool righter, lefter;
  float delay_turn;
  bool dead;
  public:

  Jeep(float tx, float ty, float an=0) {
    x = tx; y = ty;
    angle = 0;
    speed = 0;
    max_speed = 5;

    int r;
    r = randn(12);
    if (r <= 5) r = 1;
    if (r > 5) r-= 5;

    life = r;
    sc = 0.9+(r*0.1);

    base = AddVObject(new VObject(new AnimatedSprite("Data/Graphics/Jeep"), x, y, sc, 0));
    base->SetShadow(4);

    float mr, mg, mb;
    mr = randn(40) / 100.0;
    mg = randn(40) / 100.0;
    mb = randn(40) / 100.0;


    ALLEGRO_COLOR t;
    t = al_map_rgb_f(1.0-mr, 1.0-mg, 1.0-mb);
    base->SetTint(t);

    shoot_delay = 0;
    print_delay = 0;
    dead=false;
    angle = an;



    delay_turn = 0;
  }

  void Update();

  float GetX() {return x;}
  float GetY() {return y;}

  float GetScale() {return sc;}

  float Explode() {
    dead=true;
    int v;
    v = randn(3); GetSound("Data/Sounds/Explosion" + ToString(v) + ".ogg")->Play();

    v = randn(3);
    VObject *s;
    s = AddVObject(new VObject(GetBitmap("Data/Graphics/Explosion"+ToString(v)+".png"), x, y, 0.7, 0));
    s->AnimateScale(5.0, 100);
    s->SetTint(al_map_rgba_f(1.0, 1.0, 1.0, 0.6));
    s->AnimateTint(1.0, 1.0, 1.0, 0.0, 50);
    s->SetLife(100);
    s->SetZ(7);

    CreateLightAt(x, y, 3);

    int sz;
    sz = 15 + randn(10);
    for (int i=0; i!=sz; i+=1) {
      float mx, my;
      mx = x + randn(200)-100;
      my = y + randn(200)-100;
      VObject *s;
      v= randn(5);
      s = AddVObject(new VObject(GetBitmap("Data/Graphics/JeepPiece"+ToString(v)+".png"), x, y, 1.0, 0));
      s->SetTint(al_map_rgba_f(1.0, 1.0, 1.0, 1.0));
      s->AnimateTint(1.0, 1.0, 1.0, 0.0, 50);
      s->AnimateMoveTo(mx, my, randn(15)+15);
      s->SetLife(100);
      s->SetZ(-0.5);
    }

    s = AddVObject(new VObject(GetBitmap("Data/Graphics/Crater" + ToString(randn(3)) + ".png"), x, y, 1.0, angle));
    s->SetTint(al_map_rgba_f(1.0, 1.0, 1.0, 1.0));
    s->AnimateTint(1.0, 1.0, 1.0, 0.0, 1000);
    s->SetLife(1000);
    s->SetZ(-0.5);

    base->SetLife(0);
    base = NULL;

    GlobalScore += 200 * sc;
  }

  void Hit() {
    life -= 1;
    if (life <= 0) {
      Explode();
    }
  }

  bool Dead() {return dead;}

  ~Jeep() {}
};


std::deque<Jeep *> jeeps;

void AddJeep(Jeep *m) {
  jeeps.push_back(m);
}


void CleanupJeeps() {
  std::deque<Jeep *> new_list;
  std::deque<Jeep *> del_list;
  new_list.clear();
  del_list.clear();
  std::deque<Jeep *>::iterator it;
  for (it=jeeps.begin(); it!=jeeps.end(); it++) {
    if ((*it)->Dead()) {
      del_list.push_back(*it);
    }
    else {
      new_list.push_back(*it);
    }
  }
  jeeps.clear();
  for (it=new_list.begin(); it!=new_list.end(); it++) {
    jeeps.push_back(*it);
  }
  for (it=del_list.begin(); it!=del_list.end(); it++) {
    delete (*it);
  }
  new_list.clear();
  del_list.clear();
}


void UpdateJeeps() {
  static float NextCleanup=50;

  NextCleanup -= DeltaTime;
  if (NextCleanup < 0) {
    NextCleanup = 60;
    CleanupJeeps();
  }

  for (std::deque<Jeep *>::iterator it=jeeps.begin(); it!=jeeps.end(); it++) {
    (*it)->Update();
  }
}



void DoWhatCrateSays(enum MissileType tp);


class Tank {
  protected:
  float x, y, angle, angle_top, speed, max_speed, smoke_delay, print_delay, shoot_delay;
  VObject *base;
  VObject *top;


  float life;

  public:

  Tank(float tx, float ty) {
    x = tx; y = ty;
    angle = 0;
    angle_top = 0;
    speed = 0;
    max_speed = 3;
    base = AddVObject(new VObject(new AnimatedSprite("Data/Graphics/TankBase"), x, y, 1.0, 0));
    base->SetShadow(4);
    base->SetMotionBlur(true);
    base->SetZ(1);
    top = AddVObject(new VObject(new AnimatedSprite("Data/Graphics/TankTop"), x, y, 1.2, 0));
    top->SetCenter(16, 46);
    top->SetMotionBlur(true);
    top->SetShadow(8);
    top->SetZ(4);
    smoke_delay = 0;
    shoot_delay = 0;
    print_delay = 0;
    life=TANK_LIFE;
  }

  float GetX() {return x;}
  float GetY() {return y;}

  float GetLife() {return life;}

  void Heal(float lf) {
    life += lf;
    if (life > 200) life = 200;
  }

  void Update() {
    if (Dead()) return;

    base->SetX(x); base->SetY(y); base->SetAngle(angle+AngleToRad(90));

    angle_top = (Vec2D(SRX(mouse_x), SRY(mouse_y))-Vec2D(x, y)).GetAngle();

    top->SetX(x); top->SetY(y); top->SetAngle(angle_top+AngleToRad(90));

    if (KB_Active[ALLEGRO_KEY_W]) speed += ((max_speed-speed)/10) * DeltaTime;
    if (KB_Active[ALLEGRO_KEY_S]) speed += (((-max_speed/2)-speed)/10) * DeltaTime;
    else speed -= ((speed)/20) * DeltaTime;

    if (KB_Active[ALLEGRO_KEY_A]) angle -= (0.04*(speed/max_speed)) * DeltaTime;
    if (KB_Active[ALLEGRO_KEY_D]) angle += (0.04*(speed/max_speed)) * DeltaTime;

    angle = FixRadAngle(angle);

    if (KB_Active[ALLEGRO_KEY_LSHIFT]) max_speed = 15;
    else max_speed = 5;

    if (speed != 0) {
      if (speed > 1) {
        smoke_delay -= DeltaTime;
        print_delay -= DeltaTime;
        if (speed > 5) {
          if (smoke_delay < 0) {
            smoke_delay = SMOKE_DELAY_TANK;

            float tx, ty;
            tx = x + cos(angle+AngleToRad(135)) * 30;
            ty = y + sin(angle+AngleToRad(135)) * 30;
            CreateDustAt(tx, ty, 0.6);

            tx = x + cos(angle+AngleToRad(225)) * 30;
            ty = y + sin(angle+AngleToRad(225)) * 30;
            CreateDustAt(tx, ty, 0.6);
          }
        }

        if (print_delay < 0) {
          print_delay = PRINT_DELAY_TANK;
          CreatePrintAt(x, y, angle+AngleToRad(90));
        }
      }
    }

    base->SetActualAnimationSpeed(speed/3.0);


    shoot_delay -= DeltaTime;
    if (shoot_delay < 0) shoot_delay = 0;
    if (KB_Active[ALLEGRO_KEY_MOUSE_LCLICK]) {
      if (shoot_delay == 0) {
        top->SetActualAnimation("Shoot");
        float tx, ty;
        tx = x + cos(angle_top) * 15;
        ty = y + sin(angle_top) * 15;
        CreateLightAt(tx, ty, 1.0);

        CreateSmokeAt(tx, ty, 0.3);

        AddMissile(new Missile(tx, ty, angle_top+AngleToRad(randn(15))-AngleToRad(7.5), BULLET));
        shoot_delay = 5;
        GetSound("Data/Sounds/MachGun.ogg")->Play();
      }
    }

    if (shoot_delay == 0) {
    if (KB_Active[ALLEGRO_KEY_MOUSE_RCLICK]) {
      if (ActualWeapon == 0) {
        top->SetActualAnimation("Shoot");
        float tx, ty;
        tx = x + cos(angle_top) * 45;
        ty = y + sin(angle_top) * 45;
        CreateLightAt(tx, ty);

        AddMissile(new Missile(tx, ty, angle_top));

        CreateSmokeAt(tx, ty);

        GetSound("Data/Sounds/ShootRocket.ogg")->Play();

        shoot_delay = 30;
      }
      if (ActualWeapon == 1) {
        Jeep *target;
        float best_distance;
        best_distance = 99999;
        target = NULL;
        for (std::deque<Jeep *>::iterator it=jeeps.begin(); it!=jeeps.end(); it++) {
          if (!(*it)->Dead()) {
            float dist;
            dist = DistanceBetweenPoints(SRX(mouse_x), SRY(mouse_y), (*it)->GetX(),  (*it)->GetY());
            if (dist < best_distance) {
              best_distance = dist;
              target = (*it);
            }
          }
        }

        if (target != NULL) {
          top->SetActualAnimation("Shoot");
          float tx, ty;
          tx = x + cos(angle_top) * 45;
          ty = y + sin(angle_top) * 45;
          CreateLightAt(tx, ty);

          AddMissile(new Missile(tx, ty, angle_top, HOMING_MISSILE, target));

          CreateSmokeAt(tx, ty);

          GetSound("Data/Sounds/ShootRocket.ogg")->Play();
          GetSound("Data/Sounds/Homing.ogg")->Play(0.5);

          VObject *s;
          s = AddVObject(new VObject(GetBitmap("Data/Graphics/Target.png"), target->GetX(), target->GetY(), 1.0, 0));
          s->AnimateScale(2.0, 50);
          s->SetTint(al_map_rgba_f(1.0, 1.0, 1.0, 1.0));
          s->AnimateTint(1.0, 1.0, 1.0, 0.0, 100);
          s->SetLife(150);
          s->SetZ(6);

          shoot_delay = 30;
          DropAmmoBy(1);
        }
      }

      if (ActualWeapon == 2) {
        top->SetActualAnimation("Shoot");
        GetSound("Data/Sounds/FlamethrowerLoop.ogg")->Play(0.5);
        float tx, ty;
        tx = x + cos(angle_top) * 45;
        ty = y + sin(angle_top) * 45;
        CreateLightAt(tx, ty);

        CreateSmokeAt(tx, ty);

        VObject *s;
        float ang;
        ang = angle_top+AngleToRad(randn(10))-AngleToRad(5);
        AddMissile(new Missile(tx, ty, ang, FLAMETHROWER));
        shoot_delay = 3;
        DropAmmoBy(1);
      }

      if (ActualWeapon == 3) {
        top->SetActualAnimation("Shoot");
        float tx, ty;
        tx = x + cos(angle_top) * 45;
        ty = y + sin(angle_top) * 45;

        for (std::deque<Jeep *>::iterator it=jeeps.begin(); it!=jeeps.end(); it++) {
          if (!(*it)->Dead()) {
            float dist;
            dist = DistanceBetweenPoints(tx, ty, (*it)->GetX(),  (*it)->GetY());
            if (dist < 500) {
              GetSound("Data/Sounds/Zap.ogg")->Play(0.5);
              VObject *s;
              float tw, ang;
              Vec2D mp;
              mp = (Vec2D(tx, ty) + Vec2D((*it)->GetX(),  (*it)->GetY()))/2;
              Vec2D d;
              d = (Vec2D((*it)->GetX(), (*it)->GetY())-Vec2D(tx, ty));
              ang = d.GetAngle();
              tw = d.GetMagnitude();
              s = AddVObject(new VObject(GetBitmap("Data/Graphics/Zap"+ToString(randn(3))+".png"), mp.x, mp.y, tw, 20, ang));
              s->SetTint(al_map_rgba_f(1.0, 1.0, 1.0, 1.0));
              s->AnimateTint(1.0, 1.0, 1.0, 0.0, 15);
              s->SetLife(20);
              s->SetZ(-0.5);
              (*it)->Hit();
              shoot_delay = 3;
              DropAmmoBy(1);
            }
          }
        }
      }

      if (ActualWeapon == 4) {
        top->SetActualAnimation("Shoot");
        //GetSound("Data/Sounds/Laser.ogg")->Play(0.5);
        Vec2D mp;
        float tx, ty;
        tx = x + cos(angle_top) * 45;
        ty = y + sin(angle_top) * 45;
        mp = Vec2D(tx, ty);
        mp.x += cos(angle_top) * 512;
        mp.y += sin(angle_top) * 512;
        VObject *s;
        s = AddVObject(new VObject(GetBitmap("Data/Graphics/Laser.png"), mp.x, mp.y, 1024, 20, angle_top));
        s->SetTint(al_map_rgba_f(1.0, 1.0, 1.0, 0.6));
        s->AnimateTint(1.0, 1.0, 1.0, 0.0, 2);
        s->SetLife(2);
        s->SetZ(6);

        mp = Vec2D(tx, ty);
        for (int i=0; i!=100; i++) {
          mp.x += cos(angle_top) * 10;
          mp.y += sin(angle_top) * 10;

          for (std::deque<Jeep *>::iterator it=jeeps.begin(); it!=jeeps.end(); it++) {
            if (!(*it)->Dead()) {
              float dist;
              dist = DistanceBetweenPoints(mp.x, mp.y, (*it)->GetX(),  (*it)->GetY());
              if (dist < 6+40*(*it)->GetScale()) {
                (*it)->Explode();
              }
            }
          }
        }

        shoot_delay = 0;
        DropAmmoBy(1);
      }

      if (ActualWeapon == 5) {
        GetSound("Data/Sounds/Alarm.ogg")->Play(0.8);
        GetSound("Data/Sounds/Nuke.ogg")->Play(1);

        for (int i=0; i!=20; i++) {
          float tx, ty;
          tx = randn((768*AspectRatio));
          ty = randn(768);
          VObject *s;
          s = AddVObject(new VObject(GetBitmap("Data/Graphics/Explosion"+ToString(randn(3))+".png"), tx, ty, 0.7, 0));
          s->AnimateScale(3.0+randn(40)/10.0, 200);
          s->SetTint(al_map_rgba_f(1.0, 1.0, 1.0, 0.6));
          s->AnimateTint(1.0, 1.0, 1.0, 0.0, 200);
          s->SetLife(200);
          s->SetZ(7);
        }


        NukeFlashOpacity=1.0;
        WhiteFlashOpacity=1.0;
        shoot_delay = 200;
        DropAmmoBy(1);

      }
    }
    }
    if (top->Finished()) top->SetActualAnimation("Idle");

    float cx, cy;
    cx = cos(angle) * (speed * DeltaTime);
    cy = sin(angle) * (speed * DeltaTime);

    x += cx;
    y += cy;

    if (x < -EXTRA_LIMIT) x = 768*(AspectRatio)+EXTRA_LIMIT-1;
    if (x > 768*(AspectRatio)+EXTRA_LIMIT) x = -EXTRA_LIMIT;

    if (y < -EXTRA_LIMIT) y = 768+EXTRA_LIMIT;
    if (y > 768+EXTRA_LIMIT) y = -EXTRA_LIMIT;


    for (std::deque<Jeep *>::iterator it=jeeps.begin(); it!=jeeps.end(); it++) {
      if (!(*it)->Dead()) {
        if (DistanceBetweenPoints(x, y, (*it)->GetX(),  (*it)->GetY()) < 40*(*it)->GetScale()) {
          (*it)->Explode();
          break;
        }
      }
    }

    for (std::deque<Box *>::iterator it=Boxes.begin(); it!=Boxes.end(); it++) {
      if (!(*it)->Dead() && ((*it)->Landed())) {
        if (DistanceBetweenPoints(x, y, (*it)->GetX(),  (*it)->GetY()) < 70) {
          GetSound("Data/Sounds/Collect.ogg")->Play();
          (*it)->Kill();
          NewObject_Opacity = 1.0;
          DoWhatCrateSays((*it)->GetType());
        }
      }
    }
  }

  void Hit() {
    //GetSound("Data/Sounds/TankImpact.ogg")->Play();
    life -= 1;
    if (life <= 0) {
      GetSound("Data/Sounds/Explosion1.ogg")->Play();
      GetSound("Data/Sounds/Explosion2.ogg")->Play();
      GetSound("Data/Sounds/Explosion3.ogg")->Play();
      VObject *s;
      int v;
      v=randn(3);
      s = AddVObject(new VObject(GetBitmap("Data/Graphics/Explosion"+ToString(v)+".png"), x, y, 1.2, 0));
      s->AnimateScale(5.0, 100);
      s->SetTint(al_map_rgba_f(1.0, 1.0, 1.0, 0.6));
      s->AnimateTint(1.0, 1.0, 1.0, 0.0, 50);
      s->SetLife(100);
      s->SetZ(7);
      for (std::deque<Jeep *>::iterator it=jeeps.begin(); it!=jeeps.end(); it++) {
        if (!(*it)->Dead()) {
          if (DistanceBetweenPoints(x, y, (*it)->GetX(),  (*it)->GetY()) < 250) {
            (*it)->Explode();
          }
        }
      }

      base->SetLife(0);
      top->SetLife(0);

      int sz;
      sz = 75;
      for (int i=0; i!=sz; i+=1) {
        float mx, my;
        mx = x + randn(250)-125;
        my = y + randn(250)-125;
        VObject *s;
        v= randn(5);
        s = AddVObject(new VObject(GetBitmap("Data/Graphics/JeepPiece"+ToString(v)+".png"), x, y, 1.0, 0));
        s->SetTint(al_map_rgba_f(1.0, 1.0, 1.0, 1.0));
        s->AnimateMoveTo(mx, my, randn(15)+15);
        s->SetZ(-0.5);
      }

      s = AddVObject(new VObject(GetBitmap("Data/Graphics/Crater" + ToString(randn(3)) + ".png"), x, y, 2.0, angle));
      s->SetTint(al_map_rgba_f(1.0, 1.0, 1.0, 1.0));
      s->SetZ(-0.5);
    }
  }

  bool Dead() {
    return (life <= 0);
  }
};

Tank *GameTank;


void Missile::ExplodeMissile() {
  VObject *s;
  int v;
  v=randn(3);
  s = AddVObject(new VObject(GetBitmap("Data/Graphics/Explosion"+ToString(v)+".png"), x, y, 1.2, 0));
  s->AnimateScale(5.0, 100);
  s->SetTint(al_map_rgba_f(1.0, 1.0, 1.0, 0.6));
  s->AnimateTint(1.0, 1.0, 1.0, 0.0, 50);
  s->SetLife(100);
  s->SetZ(7);
  for (std::deque<Jeep *>::iterator it=jeeps.begin(); it!=jeeps.end(); it++) {
    if (!(*it)->Dead()) {
      if (DistanceBetweenPoints(x, y, (*it)->GetX(),  (*it)->GetY()) < 250) {
        (*it)->Explode();
      }
    }
  }
}

void Missile::Update() {
  if (life <= 0) return;
  missile->SetX(x); missile->SetY(y); missile->SetAngle(angle+AngleToRad(90));
  float cx, cy;
  cx = cos(angle) * (speed * DeltaTime);
  cy = sin(angle) * (speed * DeltaTime);
  x += cx;
  y += cy;

  life -= DeltaTime;


  for (std::deque<Jeep *>::iterator it=jeeps.begin(); it!=jeeps.end(); it++) {
    if (!(*it)->Dead()) {
      if (type == FLAMETHROWER) {
        if (DistanceBetweenPoints(x, y, (*it)->GetX(),  (*it)->GetY()) < 40*(*it)->GetScale()) {
          (*it)->Explode();
          GetSound("Data/Sounds/Soldier" + ToString(randn(5))+".ogg")->Play(0.7);
          return;
        }
      }
      else {
        if (DistanceBetweenPoints(x, y, (*it)->GetX(),  (*it)->GetY()) < 15*(*it)->GetScale()) {
          if ((type == MISSILE) || (type == HOMING_MISSILE)) (*it)->Explode();
          else (*it)->Hit();
          if ((type == MISSILE) || (type == HOMING_MISSILE)) ExplodeMissile();
          life = 0;
          v_e=true;
          missile->SetLife(0);
          return;
        }
     }
    }
  }

  if (target != NULL) {
    Jeep *j;
    j = (Jeep *) target;
    if (j->Dead()) {
      life = 0;
      v_e=true;
      missile->SetLife(0);
      ExplodeMissile();
    }
    float new_angle;
    new_angle = (Vec2D(j->GetX(), j->GetY()) - Vec2D(x, y)).GetAngle();
    float dist;
    dist = AngleToRad(8.0);
    float ang, toang, ldist, rdist;
    ang = angle;
    toang = new_angle;
    rdist=0;
    ldist=0;
    dist = 4;
    if (ang > toang) {
      rdist = ALLEGRO_PI*2 - ang + toang;
      ldist = ang - toang;
    }
    if (ang < toang) {
      rdist = toang - ang;
      ldist = ang + (ALLEGRO_PI*2 - toang);
    }
    if (rdist < ldist) {
      if (rdist > AngleToRad(dist)) {
        angle += AngleToRad(dist)*DeltaTime;
      }
      else {
        angle = new_angle;
      }
    }
    if (ldist < rdist) {
      if (ldist > AngleToRad(dist)) {
        angle -= AngleToRad(dist)*DeltaTime;
      }
      else {
        angle = new_angle;
      }
    }
  }

  if (!GameTank->Dead()) {
  if (DistanceBetweenPoints(x, y, GameTank->GetX(),  GameTank->GetY()) < 25) {
    GameTank->Hit();
    life = 0;
    v_e=true;
    missile->SetLife(0);
    return;
  }
  }

}


void Jeep::Update() {
  if ((!dead) && (NukeFlashOpacity > 0)) {
    Explode();
  }
  if (dead) return;
  base->SetX(x); base->SetY(y); base->SetAngle(angle+AngleToRad(270));

  speed += ((max_speed-speed)/10) * DeltaTime;

  delay_turn -= DeltaTime;
  if (delay_turn < 0) {
    delay_turn = 50;
    int v;
    v=randn(6);
    righter=lefter=false;
    if (v==2) righter = true;
    if (v==1) lefter = true;
  }

  if (righter) angle -= (0.04*(speed/max_speed)) * DeltaTime;
  if (lefter) angle += (0.04*(speed/max_speed)) * DeltaTime;

  angle = FixRadAngle(angle);
  max_speed = 3;

  if (speed != 0) {
    if (speed > 1) {
    print_delay -= DeltaTime;
    if (print_delay < 0) {
      print_delay = 2;
      CreatePrintAt(x, y, angle+AngleToRad(90), sc/2-.1);
    }
    }
  }

  angle_top = 0;

  shoot_delay -= DeltaTime;
  if (shoot_delay < 0) shoot_delay = 0;
  if (!GameTank->Dead()) {
  if (DistanceBetweenPoints(GameTank->GetX(), GameTank->GetY(), x, y) < 500) {
    angle_top = (Vec2D(GameTank->GetX(), GameTank->GetY()) - Vec2D(x, y)).GetAngle();
    if (shoot_delay == 0) {
      float tx, ty;
      tx = x + cos(angle_top) * 25;
      ty = y + sin(angle_top) * 25;
      CreateLightAt(tx, ty, 1.0);

      CreateSmokeAt(tx, ty, 0.3);

      AddMissile(new Missile(tx, ty, angle_top, BULLET));
      shoot_delay = 15+randn(10);
      GetSound("Data/Sounds/MachGun.ogg")->Play();
    }
  }
  }

  float cx, cy;
  cx = cos(angle) * (speed * DeltaTime);
  cy = sin(angle) * (speed * DeltaTime);

  x += cx;
  y += cy;

  if (x < -EXTRA_LIMIT) x = 768*(AspectRatio)+EXTRA_LIMIT;
  if (x > 768*(AspectRatio)+EXTRA_LIMIT) x = -EXTRA_LIMIT;

  if (y < -EXTRA_LIMIT) y = 768+EXTRA_LIMIT;
  if (y > 768+EXTRA_LIMIT) y = -EXTRA_LIMIT;
}

void StartGame() {
  background = AddVObject(new VObject(GetBitmap("Data/Graphics/Background.png"), SRX(SCREEN_W/2), 384, 768*AspectRatio, 768, 0));
  background->SetZ(-1);

  VObject *s=AddVObject(new VObject(GetBitmap("Data/Graphics/Controls.png"), SCREEN_W-RSX(120), 50, 1.0, 0));
  s->SetZ(10);
  s->SetTint(al_map_rgba_f(1,1,1,0.8));

  GameTank = new Tank(400, 400);

  ChangeMusic("Data/Musics/Track1.ogg");
  ChangeMusicVolume(1);

  ActualWeapon = 0;

  Weapons.push_back(Weapon(0, MISSILE));
  Weapons.push_back(Weapon(0, HOMING_MISSILE));
  Weapons.push_back(Weapon(0, FLAMETHROWER));
  Weapons.push_back(Weapon(0, ZAP_GUN));
  Weapons.push_back(Weapon(0, LASER_GUN));
  Weapons.push_back(Weapon(0, NUCLEAR_MISSILE));
}


void DrawGame() {
  DrawVisualObjects();

  GetFont("Data/Fonts/GameFont.ttf")->PrintOutline("Score: " + ToString(GlobalScore), 15, 15, 24);
  GetFont("Data/Fonts/GameFont.ttf")->PrintOutline("FPS: " + ToString(FPS), 15, 45, 24);
  GetFont("Data/Fonts/GameFont.ttf")->PrintOutline("Difficulty: " + ToString((int)DifficultyScaling), 15, 75, 24);

  GetFont("Data/Fonts/GameFont.ttf")->PrintOutline("Life: " + ToString(GameTank->GetLife()), 15, SCREEN_H-38, 28, ALLEGRO_ALIGN_LEFT, MixColor(al_map_rgba_f(0.0, 1.0, 0.0, 1.0), al_map_rgba_f(1.0, 0.0, 0.0, 1.0), (GameTank->GetLife()+1)/201.0));


  GetFont("Data/Fonts/GameFont.ttf")->Print("Dune Smasher", SCREEN_W-13, SCREEN_H-50, 42, ALLEGRO_ALIGN_RIGHT, al_map_rgb_f(0, 0, 0));
  GetFont("Data/Fonts/GameFont.ttf")->Print("Dune Smasher", SCREEN_W-15, SCREEN_H-52, 42, ALLEGRO_ALIGN_RIGHT);

  if (NewObject_Opacity > 0) {
    GetFont("Data/Fonts/GameFont.ttf")->Print(NewObject, SCREEN_W/2, 16, 44, ALLEGRO_ALIGN_CENTRE, al_map_rgba_f(0.3, 0.3, 1.0, NewObject_Opacity));
  }

  GetBitmap("Data/Graphics/Weapon.png")->DrawTransformed(SCREEN_W/2-RSX(80), SCREEN_H-RSY(40), RSX(60), RSY(60),0);
  GetBitmap("Data/Graphics/X.png")->DrawTransformed(SCREEN_W/2-RSX(20), SCREEN_H-RSY(40), RSX(60), RSY(60),0);

  GBitmap *bit;

  switch (ActualWeapon) {
    case 0 :
      bit = GetBitmap("Data/Graphics/Weapons/Missile.png");
      break;
    case 1 :
      bit = GetBitmap("Data/Graphics/Weapons/HomingMissile.png");
      break;
    case 2 :
      bit = GetBitmap("Data/Graphics/Weapons/Flamethrower.png");
      break;
    case 3 :
      bit = GetBitmap("Data/Graphics/Weapons/ZapGun.png");
      break;
    case 4 :
      bit = GetBitmap("Data/Graphics/Weapons/LaserGun.png");
      break;
    case 5 :
      bit = GetBitmap("Data/Graphics/Weapons/NuclearMissile.png");
      break;
  }

  bit->DrawTransformed(SCREEN_W/2-RSX(80), SCREEN_H-RSY(40), RSX(60), RSY(60),0);

  if (ActualWeapon == 0) GetFont("Data/Fonts/GameFont.ttf")->PrintOutline("Inf.", SCREEN_W/2+RSX(33), SCREEN_H-RSY(67), 42, ALLEGRO_ALIGN_LEFT);
  if (ActualWeapon != 0) GetFont("Data/Fonts/GameFont.ttf")->PrintOutline(ToString(Weapons[ActualWeapon].ammo), SCREEN_W/2+RSX(33), SCREEN_H-RSY(67), 42, ALLEGRO_ALIGN_LEFT);

  for (int x=0; x!=6; x++) {
    if ((Weapons[x].ammo > 0) ||(x==0)) {
    switch (x) {
    case 0 :
      bit = GetBitmap("Data/Graphics/Weapons/Missile.png");
      break;
    case 1 :
      bit = GetBitmap("Data/Graphics/Weapons/HomingMissile.png");
      break;
    case 2 :
      bit = GetBitmap("Data/Graphics/Weapons/Flamethrower.png");
      break;
    case 3 :
      bit = GetBitmap("Data/Graphics/Weapons/ZapGun.png");
      break;
    case 4 :
      bit = GetBitmap("Data/Graphics/Weapons/LaserGun.png");
      break;
    case 5 :
      bit = GetBitmap("Data/Graphics/Weapons/NuclearMissile.png");
      break;
    }
    GetBitmap("Data/Graphics/Weapon.png")->DrawTransformed(SCREEN_W/2-RSX(360)+x*RSX(45), SCREEN_H-RSY(30), RSX(35), RSY(35),0);
    bit->DrawTransformed(SCREEN_W/2-RSX(360)+x*RSX(45), SCREEN_H-RSY(30), RSX(35), RSY(35),0);
    GetFont("Data/Fonts/GameFont.ttf")->PrintOutline(ToString(x+1), SCREEN_W/2-RSX(360)+x*RSX(45)+RSX(10), SCREEN_H-RSY(20), 16, ALLEGRO_ALIGN_CENTRE);
    }
  }



  if (ShownScore) GetFont("Data/Fonts/GameFont.ttf")->PrintOutline("Press F12 to save to .png", SCREEN_W/2, SCREEN_H-40, 32, ALLEGRO_ALIGN_CENTRE, al_map_rgba_f(1.0, 1.0, 1.0, 1.0));

  GetBitmap("Data/Graphics/Target.png")->DrawTransformedMotionBlur(mouse_x, mouse_y, RSX(40), RSY(40), 0, 0, Vec2D(mouse_x, mouse_y)-Vec2D(last_mouse_x, last_mouse_y));

  if (NukeFlashOpacity > 0) {
    OpacityBlend(NukeFlashOpacity);
    GetBitmap("Data/Graphics/NukeCloud.png")->DrawTransformed(SCREEN_W/2, SCREEN_H/2, SCREEN_W, SCREEN_H, 0);
    NormalBlend();
  }
  if (WhiteFlashOpacity > 0) {
    al_draw_filled_rectangle(0, 0, SCREEN_W, SCREEN_H, al_map_rgba_f(1,1,1,WhiteFlashOpacity));
  }
}


const int MAX_JEEPS=22;

GBITMAP *ScoreText;

void ShowScore() {
  ShownScore=true;

  GBITMAP *text;
  text = al_create_bitmap(600, 300);
  DrawTo(text);
  al_clear_to_color(al_map_rgba_f(0,0,0,0));
  GetFont("Data/Fonts/GameFont.ttf")->PrintOutline("Game Over", 300, 10, 96, ALLEGRO_ALIGN_CENTRE, al_map_rgba_f(1.0, 0.3, 0.3, 1.0));


  GetFont("Data/Fonts/GameFont.ttf")->PrintOutline("Score: " + ToString(GlobalScore), 300, 210, 84, ALLEGRO_ALIGN_CENTRE, al_map_rgba_f(1.0, 1.0, 1.0, 1.0));


  DrawNormal();
  ScoreText = text;

  VObject *s=AddVObject(new VObject(new GBitmap("ScoreShow", text), SCREEN_W/2, SCREEN_H/2, 0.5, 0));
  s->AnimateScale(2, 150);
  s->SetTint(al_map_rgba_f(1.0, 1.0, 1.0, 0.0));
  s->AnimateTint(1,1,1,1, 150);
  s->SetZ(10);
}


const float SCROLL_RESISTANCE=0.9;

void DoWhatCrateSays(enum MissileType tp) {
  switch (tp) {
    case HEALTH :
      NewObject = "Healed!";
      GameTank->Heal(20);
      break;
    case HOMING_MISSILE :
      NewObject = "Received Homing Missiles!";
      Weapons[1].ammo += 8;
      ActualWeapon = 1;
      break;
    case FLAMETHROWER :
      NewObject = "You got Flamethrower ammo!";
      Weapons[2].ammo += 35;
      ActualWeapon = 2;
      break;
    case ZAP_GUN :
      NewObject = "Zap gun stashed in your armory!";
      Weapons[3].ammo += 40;
      ActualWeapon = 3;
      break;
    case LASER_GUN :
      NewObject = "Devastating Laser gun received!";
      Weapons[4].ammo += 90;
      ActualWeapon = 4;
      break;
    case NUCLEAR_MISSILE :
      NewObject = "ATTENTION: You have the Nuclear Missile!";
      Weapons[5].ammo += 1;
      ActualWeapon = 5;
      break;
  }
}

void ChangeToWeapon(int i) {
  if (Weapons[i].ammo > 0) ActualWeapon = i;
}

void ChangeWeapon(int i) {
  ActualWeapon += i;
  if (ActualWeapon >= Weapons.size()) ActualWeapon = Weapons.size()-1;
  if (ActualWeapon < 0) ActualWeapon = 0;

  while ((Weapons[ActualWeapon].ammo == 0) && (Weapons[ActualWeapon].type != MISSILE)) {
    ActualWeapon += i;
    if (ActualWeapon >= Weapons.size()) {
      ActualWeapon = Weapons.size()-1;
      if (Weapons[ActualWeapon].ammo == 0) ActualWeapon = 0;
      break;
    }
    if (ActualWeapon < 0) {
      ActualWeapon = 0;
      break;
    }
  }
}


void UpdateGame() {
  GameTank->Update();
  UpdateMissiles();
  UpdateJeeps();
  UpdateVisualObjects();
  UpdateBoxes();


  if (!GameTank->Dead()) GlobalScore+=1;

  if ((KB_Input[ALLEGRO_KEY_F12]) && (ShownScore)) {
    std::string filename;
    time_t time_date;
    struct tm *current_time;
    time_date = time(NULL);
    current_time = localtime(&time_date);
    float year, month, day, hour, minute, second;
    year     = current_time->tm_year;
    month   = current_time->tm_mon;
    day   = current_time->tm_mday;
    hour     = current_time->tm_hour;
    minute   = current_time->tm_min;
    second   = current_time->tm_sec;
    filename = "Screen" + ToString(day) + "_" + ToString(month) + " - " + ToString(hour) + "_" + ToString(minute) + "_" + ToString(second) + ".png";
    al_save_bitmap(filename.c_str(), ScoreText);
  }

  static float next_jeep_check=0;
  next_jeep_check -= DeltaTime;
  if ((next_jeep_check <= 0) && (jeeps.size() < (MAX_JEEPS+DifficultyScaling/20))) {
    next_jeep_check = 30+randn(40)-DifficultyScaling;
    if (next_jeep_check < 20) next_jeep_check = 20;
    float tx, ty;
    tx = randn(100)-50;
    ty = randn(100)-50;
    if (tx > 0) tx += 768*AspectRatio;
    if (ty > 0) ty += 768;
    float an;
    an=AngleToRad(randn(360));
    AddJeep(new Jeep(tx, ty, an));
  }

  static float next_crate_spawn=300;
  next_crate_spawn -= DeltaTime;
  if (next_crate_spawn <= 0) {
    next_crate_spawn = 400+randn(400);
    float tx, ty;
    tx = 512+randn(600)-300; ty = 384+randn(600)-300;
    AddBox(new Box(tx, ty));
  }

  DifficultyScaling += 0.001 * DeltaTime;

  NewObject_Opacity -= DeltaTime * 0.006;
  if (NewObject_Opacity < 0) NewObject_Opacity = 0;

  WhiteFlashOpacity-= DeltaTime * 0.01;
  if (WhiteFlashOpacity < 0) WhiteFlashOpacity = 0;

  NukeFlashOpacity-= DeltaTime * 0.002;
  if (NukeFlashOpacity < 0) NukeFlashOpacity = 0;

  if (((last_mouse_z - mouse_z) > SCROLL_RESISTANCE) || (KB_Input[ALLEGRO_KEY_LCTRL])) ChangeWeapon(-1);
  if (((last_mouse_z - mouse_z) < -SCROLL_RESISTANCE)  || (KB_Input[ALLEGRO_KEY_ALT])) ChangeWeapon(1);

  if (KB_Input[ALLEGRO_KEY_1]) ChangeToWeapon(0);
  if (KB_Input[ALLEGRO_KEY_2]) ChangeToWeapon(1);
  if (KB_Input[ALLEGRO_KEY_3]) ChangeToWeapon(2);
  if (KB_Input[ALLEGRO_KEY_4]) ChangeToWeapon(3);
  if (KB_Input[ALLEGRO_KEY_5]) ChangeToWeapon(4);
  if (KB_Input[ALLEGRO_KEY_6]) ChangeToWeapon(5);

  if ((!ShownScore) && (GameTank->Dead())) ShowScore();
}



