/* enemy.c - Enemy stuff for gradient wars

   No license - Use as you wish

   By James Stanley */

#include "enemy.h"

enemy_t *enemy_list;
enemy_t *last_enemy;

const int radius[ENEMIES] = { /* Drone */ 8, /* Droid */ 12, /* Turret */ 16, /* Bullet */ 4, /* Turret droid */ 16, /* Rocket */ 16, /* Dumb drone */ 8, /* Asteroid */ 32, /* Small asteroid */ 8, /* Snake head */ 4, /* Snake body */ 4 };
const int score[ENEMIES]  = {             5,             8,               25,              3,                    20,              50,                  5,                12,                      8,                  40,                 25 };

void new_enemy(fixed x, fixed y, fixed xs, fixed ys, fixed angle, int type, int damage) {
  enemy_t *enemy;
  
  if((type != SMALLAST) && (type != BULLET) && (type != SNAKEBODY) && (type != SNAKEHEAD)) {
    if(((px - x) < itofix(100)) && ((px - x) > 0)) x = px - itofix(100);
    if(((x - px) < itofix(100)) && ((x - px) > 0)) x = px + itofix(100);
    if(((py - y) < itofix(100)) && ((py - y) > 0)) y = py - itofix(100);
    if(((y - py) < itofix(100)) && ((y - px) > 0)) y = py + itofix(100);
  }
  
  enemy = malloc(sizeof(enemy_t));
  enemy->x = x;
  enemy->y = y;
  enemy->xs = xs;
  enemy->ys = ys;
  enemy->angle = angle;
  enemy->type = type;
  enemy->cooldown = 0;
  enemy->damage = damage;
  enemy->next = NULL;
  
  if(gamemode == ROCKETS) enemy->cooldown = 100;
  
  if(type == ASTEROID) enemy->cooldown = 20;//Asteroids use cooldown to store how healthy they are
  
  if(enemy_list) {
    enemy->prev = last_enemy;
    last_enemy->next = enemy;
  } else {
    enemy->prev = NULL;
    enemy_list = enemy;
  }
  
  last_enemy = enemy;
}

void del_enemy(enemy_t *enemy) {
  if(enemy->next) {//If there's a next enemy, update it's prev pointer
    enemy->next->prev = enemy->prev;
  } else {//enemy == last_enemy
    last_enemy = enemy->prev;
    if(last_enemy) last_enemy->next = NULL;
  }
  
  if(enemy->prev) {
    enemy->prev->next = enemy->next;
  } else {//enemy == enemy_list
    enemy_list = enemy->next;
    if(enemy_list) enemy_list->prev = NULL;
  }
  
  free(enemy);
}

void draw_enemies(BITMAP *canvas) {
  enemy_t *enemy;
  int ex, ey;
  
  for(enemy = enemy_list; enemy; enemy = enemy->next) {
    ex = fixtoi(enemy->x);
    ey = fixtoi(enemy->y);
    if((ex > (-offsetx)) && (ex < ((-offsetx)+SCR_WIDTH))) {
      if((ey > (-offsety)) && (ey < ((-offsety)+SCR_HEIGHT))) {
        rotate_sprite(canvas, enemy_img[enemy->type], offsetx+(ex-radius[enemy->type]), offsety+(ey-radius[enemy->type]), enemy->angle);
      }
    }
  }
}

void update_enemies(void) {
  enemy_t *enemy = enemy_list;
  enemy_t *enemy_next;
  bullet_t *bullet = bullet_list;
  int dist;
  int xdiff, ydiff;
  fixed angle;
  static char buff[32];//static so we don't have to allocate 32 bytes every time we enter the function
  
  while(enemy) {
    enemy_next = enemy->next;
    
    enemy->x += enemy->xs;
    enemy->y += enemy->ys;
    
    if((enemy->cooldown) && (enemy->type != ASTEROID)) enemy->cooldown--;
    
    if((enemy->type != SNAKEHEAD) && (enemy->type != SNAKEBODY)) {
      if(enemy->x < itofix(0)) enemy->xs += itofix(1);
      else if(enemy->x > itofix(WIDTH)) enemy->xs -= itofix(1);
      
      if(enemy->y < itofix(0)) enemy->ys += itofix(1);
      else if(enemy->y > itofix(HEIGHT)) enemy->ys -= itofix(1);
    }
    
    //Collision with bullets
    for(bullet = bullet_list; bullet; bullet = bullet->next) {
      xdiff = fixtoi(bullet->x - enemy->x);
      ydiff = fixtoi(bullet->y - enemy->y);
      dist = (xdiff * xdiff) + (ydiff * ydiff);
      if(dist < (radius[enemy->type]+6)*(radius[enemy->type]+6)) {//Collision - radius[] for enemy, 6 for bullet, squared because we're cheatin' on pythagoras
        pscore += score[enemy->type];
        snprintf(buff, 32, "%d", score[enemy->type]);
        display_text(buff, fixtoi(enemy->x), fixtoi(enemy->y));
        new_explosion(bullet->x, bullet->y);
        del_bullet(bullet);
        if(enemy->type == ASTEROID) {//Asteroids explode in to clouds of dumb drones
          if(enemy->cooldown <= 0) {
            for(angle = 0; angle < itofix(256); angle += itofix(32)) {
              new_enemy(enemy->x, enemy->y, fixcos(angle + itofix(rand()%16))*3, fixsin(angle + itofix(rand()%16))*3, itofix(rand()%255), SMALLAST, 2);
            }
            del_enemy(enemy);//Delete asteroid if health <= 0
            enemy = NULL;
          } else enemy->cooldown--;//Remove health of asteroid
        } else {//Delete non-asteroid
          if((enemy->type == SNAKEHEAD) || (enemy->type == SNAKEBODY)) split_snake(enemy);
          else del_enemy(enemy);
          enemy = NULL;
        }
        if(gamemode == ROCKETS) {//Spawn two rockets every time a rocket is killed
          new_enemy(itofix(rand()%WIDTH), itofix(rand()%HEIGHT), 0, 0, itofix(rand()%255), ROCKET, 50);
          new_enemy(itofix(rand()%WIDTH), itofix(rand()%HEIGHT), 0, 0, itofix(rand()%255), ROCKET, 50);
        }
        break;
      }
    }
    
    
    if(enemy) {//wasn't deleted
      //Collision with player
      xdiff = fixtoi(px - enemy->x);
      ydiff = fixtoi(py - enemy->y);
      dist = (xdiff * xdiff) + (ydiff * ydiff);
      if(dist < (radius[enemy->type]+12)*(radius[enemy->type]+12)) {//Collision - radius[enemy->type] for enemy, 12 for player, squared because we're cheatin' on pythagoras
#ifndef DEBUG_MODE
        phealth -= enemy->damage;
#endif
        if(phealth < 0) {
          plives--;
          phealth = 20;
          spawncount = -100;
          kill_enemies(0);
          kill_powerups();
          px = 320; py = 240;
          px = itofix(WIDTH/2); py = itofix(HEIGHT/2);
          pxs = 0; pys = 0;
          pangle = 0; pthrust = 0;
          if(gamemode == ROCKETS) {//New rocket
            new_enemy(itofix(rand()%WIDTH), itofix(rand()%HEIGHT), 0, 0, itofix(rand()%255), ROCKET, 50);
          }
          return;// <_<
        } else {
          new_explosion(enemy->x, enemy->y);
          if((enemy->type == SNAKEHEAD) || (enemy->type == SNAKEBODY)) split_snake(enemy);
          else del_enemy(enemy);
          enemy = NULL;
        }
      }
    }
    
    if(enemy) control_enemy(enemy);//NOTE: This function may delete the enemy
    
    enemy = enemy_next;
  }
}

void control_enemy(enemy_t *enemy) {
  fixed angle;
  fixed xs, ys;
  
  angle = fixatan2(py - enemy->y, px - enemy->x);
  
  switch(enemy->type) {
    case DRONE:
      enemy->angle += itofix(5);
      if(enemy->x < px) enemy->xs = itofix(1);
      if(enemy->x > px) enemy->xs = itofix(-1);
      if(enemy->y < py) enemy->ys = itofix(1);
      if(enemy->y > py) enemy->ys = itofix(-1);
      break;
    case DROID:
      enemy->angle += itofix(2);
      enemy->xs = fixcos(angle) * 4;
      enemy->ys = fixsin(angle) * 4;
      break;
    case TURRET:
      enemy->xs = 0;
      enemy->ys = 0;
      
      angle -= enemy->angle;
      
      if(angle < -itofix(2)) enemy->angle -= itofix(2);
      else if(angle > itofix(2)) enemy->angle += itofix(2);
      else if(enemy->cooldown <= 0) {
        enemy->cooldown = 50;
        angle += enemy->angle;
        xs = fixcos(angle) * 4;
        ys = fixsin(angle) * 4;
        play_sample(enemyshoot, 255, 127, 1000, 0);
        new_enemy(enemy->x, enemy->y, xs, ys, angle, BULLET, 2);
      }
      break;
    case BULLET:
      if(enemy->x < itofix(0)) del_enemy(enemy);
      else if(enemy->x > itofix(WIDTH)) del_enemy(enemy);
      else if(enemy->y < itofix(0)) del_enemy(enemy);
      else if(enemy->y > itofix(HEIGHT)) del_enemy(enemy);
      break;
    case TURRETDROID:
      enemy->xs = fixcos(angle) * 2;
      enemy->ys = fixsin(angle) * 2;
      
      enemy->angle = angle;
      
      if(enemy->cooldown <= 0) {
        enemy->cooldown = 20;
        xs = fixcos(angle) * 6;
        ys = fixsin(angle) * 6;
        play_sample(enemyshoot, 255, 127, 1000, 0);
        new_enemy(enemy->x, enemy->y, xs, ys, angle, BULLET, 5);
      }
      break;
    case ROCKET:
      if(!enemy->cooldown) {//Don't move until we are ready
        new_thrust(enemy->x, enemy->y, enemy->angle-itofix(128));//Hahaa
        
        enemy->xs += fixcos(angle) >> 1;
        enemy->ys += fixsin(angle) >> 1;
        
        enemy->xs = fixmul(enemy->xs, 0xf333/*ftofix(0.95)*/);
        enemy->ys = fixmul(enemy->ys, 0xf333/*ftofix(0.95)*/);
        
        if(enemy->xs > itofix(9)) enemy->xs = itofix(9);
        if(enemy->ys > itofix(9)) enemy->ys = itofix(9);
        
        enemy->angle = angle;
      }
      break;
    case DUMBDRONE: case ASTEROID: case SMALLAST://Asteroids and dumb drones rotate slowly
      enemy->angle += itofix(5);
      break;
    case SNAKEHEAD:
      if(!enemy->cooldown) {
        enemy->cooldown = 6;
        //Decide where to move
        if(enemy->x < itofix(24)) {//Firstly avoid the edges
          enemy->angle = RIGHT;
        } else if(enemy->x > itofix(WIDTH-24)) {
          enemy->angle = LEFT;
        } else if(enemy->y < itofix(24)) {
          enemy->angle = DOWN;
        } else if(enemy->y > itofix(HEIGHT-24)) {
          enemy->angle = UP;
        } else if((rand()%5) == 0) {//Now aim for the player
          if(px < (enemy->x - itofix(4))) enemy->angle = LEFT;
          else if(py < (enemy->y - itofix(4))) enemy->angle = UP;
          else if(px > (enemy->x + itofix(4))) enemy->angle = RIGHT;
          else enemy->angle = DOWN;
        } else if((rand()%10) == 0) {//And sometimes move randomly
          enemy->angle = itofix((rand()%4)*64);//See enemy.h snake directions to understand this
        }
        //Now move
        switch(enemy->angle) {
          case UP:    enemy->y -= itofix(8); break;
          case DOWN:  enemy->y += itofix(8); break;
          case LEFT:  enemy->x -= itofix(8); break;
          case RIGHT: enemy->x += itofix(8); break;
        }
      }
      break;
    case SNAKEBODY:
      if(!enemy->cooldown) {
        enemy->cooldown = 6;
        enemy->x = enemy->next->x;
        enemy->y = enemy->next->y;
        enemy->angle = enemy->next->angle;
      }
      break;
  }
}

void spawn_enemy(int points) {
  int xs, ys;
  int x, y;
  int i;
  fixed angle;
  
  if(points < levels[0]) new_enemy(itofix(rand()%WIDTH), itofix(rand()%HEIGHT), 0, 0, itofix(rand()%255), DRONE, 10);
  else if(points < levels[1]) {
    if(rand()%2) {// != 0
      spawn_enemy(levels[0]-1);
    } else {
      new_enemy(itofix(rand()%WIDTH), itofix(rand()%HEIGHT), 0, 0, itofix(rand()%255), DROID, 10);
    }
  } else if(points < levels[2]) {
    if(rand()%8) {// != 0
      spawn_enemy(levels[1]-1);
    } else {
      new_enemy(itofix(rand()%WIDTH), itofix(rand()%HEIGHT), 0, 0, itofix(rand()%255), TURRET, 10);
    }
  } else if(points < levels[3]) {
    if(rand()%10) {// != 0
      spawn_enemy(levels[2]-1);
    } else {
      new_enemy(itofix(rand()%WIDTH), itofix(rand()%HEIGHT), 0, 0, itofix(rand()%255), TURRETDROID, 10);
    }
  } else if(points < levels[4]) {
    if(rand()%10) {// != 0
      spawn_enemy(levels[3]-1);
    } else {
      new_enemy(itofix(rand()%WIDTH), itofix(rand()%HEIGHT), 0, 0, itofix(rand()%255), ROCKET, 50);
    }
  } else if(points < levels[5]) {
    if(rand()%20) {
      spawn_enemy(levels[4]-1);
    } else {
      if(rand()%2) {//Two types of formation
        x = rand()%WIDTH;
        y = rand()%HEIGHT;
        for(ys = -1; ys < 2; ys++) {
          for(xs = -1; xs < 2; xs++) {
            new_enemy(itofix(x), itofix(y), itofix(xs), itofix(ys), itofix(rand()%255), DUMBDRONE, 1);
          }
        }
      } else {
        x = itofix(rand()%WIDTH); y = itofix(rand()%HEIGHT);
        for(angle = 0; angle < itofix(256); angle += itofix(32)) {
          new_enemy(x, y, fixcos(angle)*3, fixsin(angle)*3, itofix(rand()%255), DUMBDRONE, 1);
        }
      }
    }
  } else if(points < levels[6]) {
    if(rand()%40) {// != 0
      spawn_enemy(levels[5]-1);
    } else {
      new_enemy(itofix(rand()%WIDTH), itofix(rand()%HEIGHT), itofix((rand()%3)-1), itofix((rand()%3)-1), itofix(rand()%255), ASTEROID, 6);
    }
  } else {
    if(rand()%20) {// != 0
      spawn_enemy(levels[6]-1);
    } else {
      x = ((rand()%(WIDTH-16)) & ~7) + 8;
      y = ((rand()%(HEIGHT-16)) & ~7) + 8;
      for(i = (rand()%8)+2; i; i--) {
        new_enemy(itofix(x+(i*8)), itofix(y), 0, 0, RIGHT, SNAKEBODY, 5);
      }
      new_enemy(itofix(x), itofix(y), 0, 0, RIGHT, SNAKEHEAD, 10);
    }
  }
}

void kill_enemies(char points) {
  enemy_t *enemy;
  char buff[32];
  
  while(enemy_list) {
    if(points) {
      pscore += score[enemy_list->type];
      snprintf(buff, 32, "%d", score[enemy_list->type]);
      display_text(buff, fixtoi(enemy_list->x), fixtoi(enemy_list->y));
    }
    new_explosion(enemy_list->x, enemy_list->y);
    enemy = enemy_list->next;
    free(enemy_list);
    enemy_list = enemy;
  }
  
  last_enemy = NULL;
}

void split_snake(enemy_t *enemy) {
  if(enemy->prev) {
    if(enemy->prev->type == SNAKEBODY) enemy->prev->type = SNAKEHEAD;
  }
  
  del_enemy(enemy);
}
