#include <allegro.h>
#include <math.h>
#include <stdio.h>

#define ALLEGRO_NO_FIX_CLASS
#define MODE GFX_AUTODETECT_FULLSCREEN
#define WIDTH 1024
#define HEIGHT 768

#define BLACK makecol(0, 0, 0)
#define WHITE makecol(255, 255, 255)
#define GREEN makecol(0, 255, 0)
#define RED makecol(255, 0, 0)
#define BLUE makecol(0, 0, 255)
#define DARK_BLUE makecol(0, 64, 128)
#define LIGHT_BLUE makecol(0, 128, 255)
#define ORANGE makecol(255, 192, 0)
#define PURPLE makecol(205, 0, 255)

#define SPD 7

#define RAC 57.32484076
#define ALLEG_ANGLE 0.708333

#define MOUSE 1
#define TARGET 0

#define HITBOX 576
#define MOVEBOX 4096
#define SHOTBOX 65536

#define HIT_DELAY 1000
#define SPAWN_DELAY 35

#define TITLE "HUNTERS"
#define SUBTITLE "BETA"
#define VERSION 4
#define SFX_VOL 180

typedef char string;

//color shift function
int color_shift(int col1, int col2, int percent)
{
    int ipercent = 100 - percent;
    int n_col, rn, gn, bn;
    int r1, g1, b1, r2, g2, b2;
    //get colors
    r1 = getr(col1);
    r2 = getr(col2);
    g1 = getg(col1);
    g2 = getg(col2);
    b1 = getb(col1);
    b2 = getb(col2);
    //shift
    rn = ((r1*percent)/100)+((r2*ipercent)/100);
    gn = ((g1*percent)/100)+((g2*ipercent)/100);
    bn = ((b1*percent)/100)+((b2*ipercent)/100);
    //create
    n_col = makecol(rn, gn, bn);
    //return
    return n_col;
}

//character class
class character
{
public:
//location
int x, y, dir;
//speeds
int x_spd, y_spd;
//other
int hp, type, var, explosion, enterance;
//target
character *target;
//AI
void action();
//movement
int move();
void set_dir(int index);
};
//movement
int character::move()
{
    int work = 1;
    x += x_spd;
    y += y_spd;
    if (y < 0)
    {
        y = 0;
        work = 0;
    }
    if (y > HEIGHT)
    {
        y = HEIGHT;
        work = 0;
    }
    if (x < 0)
    {
        x = 0;
        work = 0;
    }
    if (x > WIDTH)
    {
        x = WIDTH;
        work = 0;
    }
    return work;
}
void character::set_dir(int index)
{
    float dx, dy;
    if (index == MOUSE)
    {
        dx = mouse_x - x;
        dy = mouse_y - y;
    }
    if (index == TARGET)
    {
        dx = (*target).x - x;
        dy = (*target).y - y;
    }
    if (dx != 0)
    {
        dir = int(RAC*atan(dy/dx));
        dir += 360;
        dir %= 360;
        
        if (dx < 0)
        {
                dir += 180;
                dir %= 360;
        }
        else if (dx == 0)
        {
        if (dy > 0)
                dir = 90;
        if (dy < 0)
                dir = 270;
        }
    }
    return;
}

//wave class
class group
{
public:
int enemy[4];
};

//mission class
class mission
{
public:
int num_waves;
group *wave[100];
void clear_mission();
int load_mission(const char *filename);
};
//clean up
void mission::clear_mission()
{
    for(int i = 0; i < num_waves; i++)
    {
        delete(wave[i]);
        wave[i] = NULL;
    }
}

//distance function
int dist(int x1, int y1, int x2, int y2)
{
    int dx = x1 - x2, dy = y1 - y2;
    int gd = 0, rd = 0;

    if (dx < 0)
        dx *= -1;
    if (dy < 0)
        dy *= -1;

    if (dx > dy)
        gd = dx;
    else
        gd = dy;

    rd = (gd + dy + dx)/2;

    return rd;
}







//variables
const int MAX_HP[] = {100, 3, 1, 3, 5};
BITMAP *buffer;
SAMPLE *blast, *pshot, *eshot, *slide;
FONT *title, *text;
long log_time, loop_num, last_hit;
bool end_game = false;
string *lvl[20];
string *wpn[3];
int arms[3] = {0, 1, 2};
int iarm;
int weapon = 0, spawn_delay = 0, color = BLACK;
int progress = 0;
int lvl_clear = 0;
float sine[360];
float cosine[360];
char test_mission[240];

character player;
character shot[40];
character enemy[80];
mission n_lvl;

//define a hexadecimal conversion table
char hex[16] = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 67, 68, 69, 70};







//spawn enemies
void spawn(int type)
{
    //the angle at which the enemies spawn
    int s_angle;

    for(int i = 0; i < 80; i++)
    {
        if (enemy[i].hp <= 0)
        {
                s_angle = rand()%360;
                enemy[i].x = WIDTH/2 + int(((WIDTH/2)-rand()%75)*(cos(s_angle/RAC)));
                enemy[i].y = HEIGHT/2 + int(((HEIGHT/2)-rand()%75)*(sin(s_angle/RAC)));
                enemy[i].target = &player;
                enemy[i].type = type;
                enemy[i].hp = MAX_HP[type];
                enemy[i].explosion = 0;
                enemy[i].enterance = -5;
                if (type != 2)
                    enemy[i].var = (rand()%80)-40;
                return;
        }
    }
    return;
}

//spawn a wave of enemies
void spawn_wave(group *wave)
{
    for (int i = 0; i < 4; i++)
    {
        for (int n = 0; n < (*wave).enemy[i]; n++)
        {
                spawn(i+1);
        }
    }
    return;
}

//collsion detection
int collision(character p1, character p2, int hit_box)
{
    int dy, dx;

    dx = p1.x - p2.x;
    dy = p1.y - p2.y;

    dx *= dx;
    dy *= dy;

    if ((dx < hit_box)&&(dy < hit_box))
        return 1;
    else
        return 0;
}

//repel
void repel(character *p1, character *p2, float force)
{
    float dy, dx;
    int dir;
    
    dx = (*p1).x - (*p2).x;
    dy = (*p1).y - (*p2).y;

    if (dx != 0)
    {
        dir = int(RAC*atan(dy/dx));
        dir += 360;
        dir %= 360;

        if (dx < 0)
        {
                dir += 180;
                dir %= 360;
        }
        else if (dx == 0)
        {
        if (dy > 0)
                dir = 90;
        if (dy < 0)
                dir = 270;
        }
        (*p1).x_spd += int(force*cosine[(dir)%360]);
        (*p1).y_spd += int(force*sine[(dir)%360]);

        (*p2).x_spd -= int(force*cosine[(dir)%360]);
        (*p2).y_spd -= int(force*sine[(dir)%360]);
    }
}

//missle lock-on
character *lock_on(int x, int y)
{
    int range = WIDTH*HEIGHT;
    int index = -1;
    for (int i = 0; i < 80; i++)
    {
        if ((dist(enemy[i].x, enemy[i].y, x, y) < range)&&(enemy[i].hp > 0)&&(enemy[i].type != -1))
        {
                range = dist(enemy[i].x, enemy[i].y, x, y);
                index = i;
        }
    }
    if (index == -1)
        return NULL;
    else
        return &enemy[index];
}

//fire shot
void fire(character *source, int speed)
{
    for(int i = 0; i < 40; i++)
    {
        if (shot[i].hp <= 0)
        {
                shot[i].x = (*source).x;
                shot[i].y = (*source).y;
                shot[i].dir = (*source).dir;
                shot[i].hp = 1;
                shot[i].type = -1;
                shot[i].var = speed;
                return;
        }
    }
    return;
}
//fire missle
void fire_missle(character *source)
{
    for(int i = 0; i < 40; i++)
    {
        if (shot[i].hp <= 0)
        {
                shot[i].x = (*source).x;
                shot[i].y = (*source).y;
                shot[i].x_spd = 0;
                shot[i].y_spd = 0;
                shot[i].dir = (*source).dir;
                shot[i].target = lock_on(mouse_x, mouse_y);
                shot[i].hp = 1;
                shot[i].type = -2;
                shot[i].var = 0;
                return;
        }
    }
}
//fire at angle
void angle_fire(character *source, int angle, int speed)
{
    for(int i = 0; i < 40; i++)
    {
        if (shot[i].hp <= 0)
        {
                shot[i].x = (*source).x;
                shot[i].y = (*source).y;
                shot[i].dir = (*source).dir + angle;
                shot[i].dir += 360;
                shot[i].dir %= 360;
                shot[i].hp = 1;
                shot[i].type = -1;
                shot[i].var = speed;
                return;
        }
    }
    return;
}

//enemies' fire shot
void fire_alt(character *source)
{
    for(int i = 0; i < 80; i++)
    {
        if (enemy[i].hp <= 0)
        {
                enemy[i].x = (*source).x;
                enemy[i].y = (*source).y;
                enemy[i].dir = (*source).dir;
                enemy[i].hp = 1;
                enemy[i].type = -1;
                enemy[i].var = 14;
                return;
        }
    }
    return;
}

//enemies' angle fire
void angle_fire_alt(character *source, int angle)
{
    for(int i = 0; i < 80; i++)
    {
        if (enemy[i].hp <= 0)
        {
                enemy[i].x = (*source).x;
                enemy[i].y = (*source).y;
                enemy[i].dir = angle;
                enemy[i].dir %= 360;
                enemy[i].hp = 1;
                enemy[i].type = -1;
                enemy[i].var = 14;
                return;
        }
    }
    return;
}

//enemies' fire missle
void rocket_fire_alt(character *source)
{
    for(int i = 0; i < 80; i++)
    {
        if (enemy[i].hp <= 0)
        {
                enemy[i].x = (*source).x;
                enemy[i].y = (*source).y;
                enemy[i].x_spd = -(*source).x_spd*2;
                enemy[i].y_spd = -(*source).y_spd*2;
                enemy[i].dir = ((*source).dir+180)%360;
                enemy[i].target = &player;
                enemy[i].hp = 1;
                enemy[i].type = -2;
                enemy[i].var = 0;
                return;
        }
    }
    return;
}

//drop a proximity mine
void drop_mine(character *source)
{
    for(int i = 0; i < 40; i++)
    {
        if (shot[i].hp <= 0)
        {
                shot[i].x = (*source).x;
                shot[i].y = (*source).y;
                shot[i].hp = 1;
                shot[i].dir = 0;
                shot[i].type = -3;
                shot[i].var = 1;
                return;
        }
    }
    return;
}

//enemy count
int num_enemies()
{
    int num = 0;
    for (int i = 0; i < 80; i++)
    {
        if ((enemy[i].explosion < 5)&&(enemy[i].type > 0))
            num++;
    }
    return num;
}

//ai
void character::action()
{
    switch (type)
    {
        //proximity mine
        case -3:
        {
        if ((hp < 1)&&(var == 1))
        {
                for (int o = 0; o < 8; o++)
                {
                    angle_fire(this, o*30, 12);
                }
                var = 0;
        }
        }
        break;
        //missle
        case -2:
        {
        if (target != NULL)
        {
                if ((*target).hp <= 0)
                    target = lock_on(x, y);
                if (num_enemies() > 0)
                    set_dir(TARGET);
        }
        else if (num_enemies > 0)
        {
                target = lock_on(x, y);
        }

        x_spd += int(1.6*cosine[(dir)%360]);
        y_spd += int(1.6*sine[(dir)%360]);

        if (x_spd > 8)
                x_spd = 8;
        if (x_spd < -8)
                x_spd = -8;
        if (y_spd > 8)
                y_spd = 8;
        if (y_spd < -8)
                y_spd = -8;

        move();
        var++;
        if (var > 150)
                hp = 0;
        }
        break;
        //projectile
        case -1:
        {
        x_spd = int(var*cosine[(dir)%360]);
        y_spd = int(var*sine[(dir)%360]);
        
        if (move() == 0)
                hp = 0;
        }
        break;
        //player
        case 0:
        {
        x_spd = 0;
        y_spd = 0;
        if ((key[KEY_LEFT])||(key[KEY_A]))
                x_spd -= SPD;
        if ((key[KEY_RIGHT])||(key[KEY_D]))
                x_spd += SPD;
        if ((key[KEY_UP])||(key[KEY_W]))
                y_spd -= SPD;
        if ((key[KEY_DOWN])||(key[KEY_S]))
                y_spd += SPD;

        move();
        set_dir(MOUSE);
        var--;
        //fire vulcan cannon
        if ((mouse_b & 1)&&(var <= 0)&&(weapon == 0))
        {
                fire(this, 16);
                var = 3;
        }
        //fire spreader rifle
        if ((mouse_b & 1)&&(var <= 0)&&(weapon == 1))
        {
                for (int i = 0; i < 5; i++)
                        angle_fire(this, -16 + i*6, 10);
                var = 18;
        }
        //fire railgun
        if ((mouse_b & 1)&&(var <= 0)&&(weapon == 2))
        {
                for (int i = 0; i < 7; i++)
                        fire(this, 35);
                var = 22;
        }
        //use bomb
        if ((mouse_b & 2)&&(var <= 0))
        {
                for (int i = 0; i < 36; i++)
                {
                        angle_fire(this, i*10, 40);
                }
                var = 60;
        }
        
        if (mouse_z > 0)
        {
                position_mouse_z(0);
                iarm += 1;
                iarm %= 3;
        }
        if (mouse_z < 0)
        {
                position_mouse_z(0);
                iarm += 2;
                iarm %= 3;
        }
        
        if (key[KEY_1])
                iarm = 0;
        if (key[KEY_2])
                iarm = 1;
        if (key[KEY_3])
                iarm = 2;
        weapon = arms[iarm];
        }
        break;
        //grunt
        case 1:
        {
        set_dir(TARGET);
        
        x_spd += int(1.5*cosine[(dir)%360]);
        y_spd += int(1.5*sine[(dir)%360]);
        
        if (x_spd > 4)
                x_spd = 4;
        if (x_spd < -4)
                x_spd = -4;
        if (y_spd > 4)
                y_spd = 4;
        if (y_spd < -4)
                y_spd = -4;
        
        move();
        }
        break;
        //hornet
        case 2:
        {
        set_dir(TARGET);
        
        if (var == 0)
        {
                x_spd += int(1.5*cosine[(dir)%360]);
                y_spd += int(1.5*sine[(dir)%360]);
        }
        if (var == 1)
        {
                x_spd -= int(2.5*cosine[(dir)%360]);
                y_spd -= int(2.5*sine[(dir)%360]);
        }
        
        if (collision(*this, *target, HITBOX))
        {
                var = 1;
        }
        if (!collision(*this, *target, SHOTBOX))
        {
                var = 0;
        }
        
        for (int i = 0; i < 40; i++)
        {
                if ((shot[i].hp > 0)&&(shot[i].type == -1)&&(collision(*this, shot[i], MOVEBOX)))
                {
                        repel(this, &shot[i], 3.5);
                }
        }
        
        if (x_spd > 5)
                x_spd = 5;
        if (x_spd < -5)
                x_spd = -5;
        if (y_spd > 5)
                y_spd = 5;
        if (y_spd < -5)
                y_spd = -5;
        
        move();
        }
        break;
        //sniper
        case 3:
        {
        set_dir(TARGET);
        
        x_spd += int(1.5*cosine[(dir)%360]);
        y_spd += int(1.5*sine[(dir)%360]);

        if (collision(*this, *target, SHOTBOX))
        {
                repel(this, target, 2.2);
        }
        
        if (x_spd > 2)
                x_spd = 2;
        if (x_spd < -2)
                x_spd = -2;
        if (y_spd > 2)
                y_spd = 2;
        if (y_spd < -2)
                y_spd = -2;
        
        if ((loop_num - var) % 120 == 0)
                fire_alt(this);

        move();
        }
        break;
        //juggernaut
        case 4:
        {
        set_dir(TARGET);

        x_spd += int(1.5*cosine[(dir)%360]);
        y_spd += int(1.5*sine[(dir)%360]);
        
        if (x_spd > 3)
                x_spd = 3;
        if (x_spd < -3)
                x_spd = -3;
        if (y_spd > 3)
                y_spd = 3;
        if (y_spd < -3)
                y_spd = -3;
        
        if (collision(*this, *target, MOVEBOX))
        {
                repel(this, target, -3);
        }

        move();
        }
        break;
    }
}

//play a sound
void playsound(SAMPLE *spl)
{
    if (spl == NULL)
        return;
    play_sample(spl, SFX_VOL, 128, 1000, 0);
}

//load a mission
int mission::load_mission(const char *filename)
{
    num_waves = 0;
    FILE *mfile = fopen(filename, "rb");
    if (mfile == NULL)
        return 0;
    int code = 0;
    fscanf(mfile, "%i\n", &code);
    if (code != 1117)
        return 0;
    fscanf(mfile, "%i\n", &num_waves);
    for (int i = 0; i < num_waves; i++)
    {
        wave[i] = new group;
        fscanf(mfile, "%i %i %i %i\n", &(*wave[i]).enemy[0], &(*wave[i]).enemy[1], &(*wave[i]).enemy[2], &(*wave[i]).enemy[3]);
    }
    fclose(mfile);
    return 1;
}
