BASE.CPP

The base.cpp file is where the code for all the classes shared by SPACE_INVADERS and GALAXIANS.
These classes are not game-specific.
Look at the text_msg class below that enhances multi-line text-output with Allegro for example.
/****************************************************************
    GALVADERS (V1.0), BY AHARON HILLEL.
    gcc version 3.2 (mingw special 20020817-1)
    ALLEGRO VERSION: 4.1.12 (WIP)
    ALLEGRO DATE:    2003-11-10
****************************************************************/
#include "base.h"
#include "inv.h"
/***********************************************
   PLACE ALL CLASSES THAT ARE USED BY BOTH
   INVADERS AND GLAXIANS IN THIS FILE.
***********************************************/


//---------  START    --  CLASSes STATIC init  --------
int enemy::enemies = 0;
int player::players = 0;
st_bullet_params player::bullet_params = { 0 }; //  Will be initialized b4 game starts.
st_pixel player::inv_bullet_hit_pixles[] = { 0,0 ,3,0, 0,6, 3,6, 0,11, 3,11 };
st_pixel player::gal_bullet_hit_pixles[] = { 2,0, 0,3, 4,3, 1,9, 3,9 };
st_pixel player::inv_player_hit_pixles[] = { 16,0, 19,0, 3,14, 32,14, 0,19, 35,19 };
st_pixel player::gal_player_hit_pixles[] = { 17,0, 5,19, 29,19, 0,23, 34,23, 0,30, 34,30 }; 
st_colors player::bullet_hit_colors =  { 13, 67 };
int formation::attack_ok = 0;
//---------  END      --  CLASSes STATIC init  --------


text_msg::text_msg()
{
    dest = NULL;
    text = NULL;
    bg_tile = NULL;
    bg_buffer = NULL;
    lines = NULL;
    longest_line = NULL;
    show_text = G_TRUE;
    x_pos = y_pos = length = font_type = floating_text = text_color = bg_color = n_lines = background_w = background_h = frames = blank_frames = frame_counter = 0;
    x_text_orientation = TEXT_LEFT; //  Left, Center, Right.
    y_text_orientation = TEXT_BOTTOM; // Bottom, Center, Top.
}

text_msg::text_msg(BITMAP *dest_bmp)
{
    dest = dest_bmp;
    text = NULL;
    bg_tile = NULL;
    bg_buffer = NULL;
    lines = NULL;
    longest_line = NULL;
    show_text = G_TRUE;
    x_pos = y_pos = length = font_type = floating_text = text_color = bg_color = n_lines = background_w = background_h = frames = blank_frames = frame_counter = 0;
    x_text_orientation = TEXT_LEFT;    //  Left, Center, Right.
    y_text_orientation = TEXT_BOTTOM;  // Bottom, Center, Top.
}

text_msg::text_msg(BITMAP *dest_bmp, char *t, int x, int y, int f, int float_flag, int tc, int bg_c, int txo, int tyo, BITMAP *bg_t, int d)
{
    text = NULL;
    dest = dest_bmp;
    show_text = G_TRUE;
    x_pos = x; y_pos = y; font_type = f;
    floating_text = float_flag;
    text_color = tc;  bg_color = bg_c;
    x_text_orientation = txo; y_text_orientation = tyo;
    frames = blank_frames = frame_counter = 0;
    bg_tile = NULL;
    bg_buffer = NULL;
    length = strlen(t);
    if(!length)
      return;

    if(bg_t)
    {  //  save a copy of the tile.
       bg_tile = create_bitmap(bg_t->w, bg_t->h);
       blit(bg_t, bg_tile, 0, 0, 0, 0, bg_t->w, bg_t->h);
    }

    text = (char *) malloc(length + 1);
    strcpy(text, t);
    get_lines();
    compute_background_size();
    
    if(d)
      draw();
}

text_msg::text_msg(BITMAP *dest_bmp, char *t, int x, int y, int f, int float_flag, int tc, int bg_c, int txo, int tyo, BITMAP *bg_t, unsigned int f_counter, unsigned int b_frames, int d)
{
    text = NULL;
    dest = dest_bmp;
    show_text = G_TRUE;
    frames = frame_counter = f_counter + b_frames;
    blank_frames = b_frames;
    x_pos = x; y_pos = y; font_type = f;
    floating_text = float_flag;
    text_color = tc;  bg_color = bg_c;
    x_text_orientation = txo; y_text_orientation = tyo;
    bg_tile = NULL;
    bg_buffer = NULL;
    length = strlen(t);
    if(!length)
      return;

    if(bg_t)
    {  //  save a copy of the tile.
       bg_tile = create_bitmap(bg_t->w, bg_t->h);
       blit(bg_t, bg_tile, 0, 0, 0, 0, bg_t->w, bg_t->h);
    }
    
    text = (char *) malloc(length + 1);
    strcpy(text, t);
    get_lines();
    compute_background_size();
    
    if(d)
      draw();
}

text_msg::text_msg(const text_msg &other)
{
    text = NULL;
    dest = other.dest;
    show_text = other.show_text;
    frames = other.frames;
    frame_counter = other.frame_counter;
    blank_frames = other.blank_frames;
    x_pos = other.x_pos;
    y_pos = other.y_pos;
    font_type = other.font_type;
    floating_text = other.floating_text;
    text_color = other.text_color;
    bg_color = other.bg_color;
    x_text_orientation = other.x_text_orientation;
    y_text_orientation = other.y_text_orientation;
    bg_tile = NULL;
    bg_buffer = NULL;
    length = other.length;
    background_w = other.background_w;
    background_h = other.background_h;
    
    if(!length)
      return;

    if(other.bg_tile)
    {  //  save a copy of the tile.
       bg_tile = create_bitmap(other.bg_tile->w, other.bg_tile->h);
       blit(other.bg_tile, bg_tile, 0, 0, 0, 0, other.bg_tile->w, other.bg_tile->h);
    }

    text = (char *) malloc(length + 1);
    strcpy(text, other.text);
    get_lines();
}

int text_msg::get_lines(void)
{
    int i;
    int temp = 0;
    char *p = text;
    n_lines = 1;
    
    longest_line = text;
    for(i = 0; i < length; i++)
      if((text[i]) == '\n')
      {
        text[i] = '\0';
        /*
        if(strlen(longest_line) < strlen(p))
          longest_line = p;
        */
        /*  Compute length in pixels instead because sometimes same-length strings have different size in graphics  */
        if(text_length((FONT *) s_data[font_type].dat, longest_line) < text_length((FONT *) s_data[font_type].dat, p))
          longest_line = p;
          
        p = &text[i+1];  
        n_lines++;
      }
    
    lines = (char **) malloc(n_lines * sizeof(char *));
    temp = 0;
    lines[temp] = text;
    for(i = 0; i < length; i++)
    { //  Save line pointers in lines array.
      if(text[i] == '\0')
      {
        temp++;
        lines[temp] = &(text[i+1]);
      }
    }
}

void text_msg::compute_background_size(void)
{
    background_w = background_h = 0;
    if(!text)
      return;
    if(!dest)
      return;

    int t_ht = text_height((FONT *) s_data[font_type].dat);
    if(bg_tile)
    { //  Compute background rect according to tile size.
      int t_width = text_length((FONT *) s_data[font_type].dat, longest_line);
      int tiles = t_width / bg_tile->w;
      if(t_width % bg_tile->w)
        tiles++;
      background_w = tiles * bg_tile->w;
      
      tiles = (n_lines * t_ht) / bg_tile->h;
      if((n_lines * t_ht) % bg_tile->h)
        tiles++;
      background_h = tiles * bg_tile->h;
    }
    else
    { //  Compute background rect according to text size.
      background_w = text_length((FONT *) s_data[font_type].dat, longest_line);
      background_h = n_lines * t_ht;
    }
}

void text_msg::set_bg_tile(BITMAP *tile)
{
    if(bg_tile)
      destroy_bitmap(bg_tile);
      
    bg_tile = NULL;
    
    if(tile)
    {  //  save a copy of the tile.
       bg_tile = create_bitmap(tile->w, tile->h);
       blit(tile, bg_tile, 0, 0, 0, 0, tile->w, tile->h);
       /*  Tile size may change so compute background size.  */
       compute_background_size();
    }
}

void text_msg::draw(void)
{
    if(!text)
      return;
    if(!dest)
      return;
     
    int text_bk_color = bg_color;
    int t_ht = text_height((FONT *) s_data[font_type].dat);
    
    int o_x_pos = x_pos; //  Assume Right orientation.
    int o_y_pos = y_pos; //  Assume Bottom orientation.
    
    if(x_text_orientation == TEXT_CENTER)
      o_x_pos -= background_w >> 1;
    if(x_text_orientation == TEXT_LEFT)
      o_x_pos -= background_w;
    
    if(y_text_orientation == TEXT_CENTER)
      o_y_pos -= background_h >> 1;
    if(y_text_orientation == TEXT_TOP)
      o_y_pos -= background_h;
    
    if(floating_text)
    { //  Save background first.
      if(bg_buffer)
        if((background_w != bg_buffer->w) || (background_h != bg_buffer->h))
        {
           destroy_bitmap(bg_buffer);
           bg_buffer = NULL;
        }  
        
      if(!bg_buffer)      
        bg_buffer = create_bitmap(background_w, background_h);
      
      blit(dest, bg_buffer, o_x_pos, o_y_pos, 0, 0, background_w, background_h);
    }
    if(bg_tile)
    {  //  First blit tile, then do the text.
       text_bk_color = -1;
       int ht; int wd;
       for(ht = 0; ht < background_h; ht += bg_tile->h)
         for(wd = 0; wd < background_w; wd += bg_tile->w)
           blit(bg_tile, dest, 0, 0, o_x_pos+wd, o_y_pos+ht, bg_tile->w, bg_tile->h);
    }

    //  Write text.
    int show_text_flag = G_TRUE;
    if(frames)  //  if this text sould blink.
    {
      if(frame_counter)
      {
        frame_counter--;
        if(frame_counter <= blank_frames)
          show_text_flag = G_FALSE;  
      }
      else
        frame_counter = frames;
    }
    if(show_text & show_text_flag)
      for(int i = 0; i < n_lines; i++)
        textout_ex(dest, (FONT *) s_data[font_type].dat, lines[i], o_x_pos, o_y_pos+(i*t_ht), text_color, text_bk_color);
}

void text_msg::remove()
{
    if(!text)
      return;
    if(!dest)
      return;
     
    int o_x_pos = x_pos; //  Assume Right orientation.
    int o_y_pos = y_pos; //  Assume Bottom orientation.
    
    if(x_text_orientation == TEXT_CENTER)
      o_x_pos -= background_w >> 1;
    if(x_text_orientation == TEXT_LEFT)
      o_x_pos -= background_w;

    if(y_text_orientation == TEXT_CENTER)
      o_y_pos -= background_h >> 1;
    if(y_text_orientation == TEXT_TOP)
      o_y_pos -= background_h;

    if(bg_buffer)  //  Restore background.
      blit(bg_buffer, dest, 0, 0, o_x_pos, o_y_pos, bg_buffer->w, bg_buffer->h);
    else          //  Fill a black rectangle.
      rectfill(dest, o_x_pos, o_y_pos, o_x_pos+background_w, o_y_pos+background_h, 0);
}

inline void text_msg::set_pos_ex(int new_x, int new_y, int new_float_flag, int d)
{
    x_pos = new_x;
    y_pos = new_y;
    if(!new_float_flag)
      if(bg_buffer)
      {
        destroy_bitmap(bg_buffer);
        bg_buffer = NULL;
      }
      
    if(d)
      draw();  
}

int text_msg::set_text(char *new_text)
{   /*  Returns length of text that was set  */
    if(lines)
      free(lines);
    lines = NULL;
    
    if(!new_text)
    {  //  Clear this string.
       if(text)
         free(text);
         
       text = NULL;
       return 0;
    }   
    
    if(strlen(new_text) != length)
    {   //  Allocate new length string.
        if(text)
          free(text);
          
        text = NULL;  
        length = strlen(new_text);
        if(!length)
          return length;
          
        text = (char *) malloc(length+1);
    }

    strcpy(text, new_text);
    get_lines();
    compute_background_size();
    return length;
}

text_msg::~text_msg()
{
    if(bg_buffer)
      destroy_bitmap(bg_buffer);

    if(bg_tile)
      destroy_bitmap(bg_tile);
            
    if(text)
      free(text);
      
    if(lines)
      free(lines);  
}
//---------  END    --  CLASS text_msg  -----------

int test_hit_pixels(BITMAP *bmp, const st_colors colors, int x, int y, st_pixel *hit_pixels, int n_hit_pixels)
{
    int i;
    int c;

    if(!bmp)
      return 0;

    for(i = 0; i < n_hit_pixels ; i++)
    {
      //c = getpixel(bmp, x + hit_pixels[i].x,y + hit_pixels[i].y);
      c = bmp->line[y + hit_pixels[i].y][x + hit_pixels[i].x];
      if(c >= colors.low_color && c <= colors.high_color)
        return c;
    }
    
    return 0;
}


int test_hit_pixels(BITMAP *bmp, const st_colors *colors, const int i_colors, int x, int y, st_pixel *hit_pixels, int n_hit_pixels)
{   /*  Same as test_hit_pixels but test against a list of colors-rages. Not used in this game  */
    int i;
    int j;
    int c;

    if(!bmp)
      return 0;

    for(i = 0; i < n_hit_pixels ; i++)
      for (j = 0 ;  j < i_colors; j++)
      {
        //c = getpixel(bmp, x + hit_pixels[i].x,y + hit_pixels[i].y);
        c = bmp->line[y + hit_pixels[i].y][x + hit_pixels[i].x];
        if(c >= colors[j].low_color && c <= colors[j].high_color)
          return c;
      }

    return 0;
}

//---------  START  --  CLASS enemy  -----------
enemy::enemy() : base_image()
{   //  Cannot be used.
    anim_type = ANIM_NONE;
    anim_dir = 0;
    enemies++;
};

enemy::enemy(RLE_SPRITE **dt, BITMAP *dst, const int x,  const int y,  const int z, const int bg_f, const int d, const st_enemy_params &p) : base_image(dt, dst, x, y, z, p.index_low, bg_f, d)
{
    enemies++;
    
    //  Init all private members from struct.
    index_low  = p.index_low;
    index_high = p.index_high;
    anim_type  = p.anim_type;

    switch(anim_type)
    {
      case ANIM_LOW_HIGH:
      case ANIM_PING_PONG:
        anim_dir = 1;
        break;

      case ANIM_HIGH_LOW:
        anim_dir = -1;
        break;

      default:
        anim_dir = 0;
    };

    x_min      = p.x_min;
    y_min      = p.y_min;
    x_max      = p.x_max;
    y_max      = p. y_max;
};

enemy::enemy(const enemy &other)
{   // COPY constructor
    enemies++;
    index_low  = other.index_low;
    index_high = other.index_high;
    anim_type  = other.anim_type;
    anim_dir = other.anim_dir;

    x_min      = other.x_min;
    y_min      = other.y_min;
    x_max      = other.x_max;
    y_max      = other. y_max;
};

enemy::~enemy()
{
    enemies--;
};

inline void enemy::inc_index(void)
{
    if(get_sprite_index() < index_high)
       set_sprite_index(get_sprite_index() + 1);
};

inline void enemy::dec_index(void)
{
    if(get_sprite_index() > index_low)
       set_sprite_index(get_sprite_index() - 1);
};

void enemy::play_anim(void)
{
    if(anim_type == ANIM_NONE)
      return;

    int t_index = get_sprite_index();
    t_index += anim_dir;
    
    if((t_index < index_low) || (t_index > index_high))
    {
      switch(anim_type)
      {
        case ANIM_LOW_HIGH:
          set_sprite_index(index_low);
          break;
        case ANIM_HIGH_LOW:
          set_sprite_index(index_high);
          break;
        case ANIM_PING_PONG:
          anim_dir *= -1;
          set_sprite_index(get_sprite_index() + anim_dir);
          break;
      }
    }
    else
      set_sprite_index(t_index);
};
int enemy::kill(void)
{
    return G_TRUE;
}

int enemy::action(void)
{
    return G_TRUE;
}
//---------  END    --  CLASS enemy  -----------


//---------  START  --  CLASS bullet  -----------
bullet::bullet() : base_image()
{   //  This cannot be used.
    hit_z_order_low = hit_z_order_high = -1;  //  invalid speed.
    score = 0;
    owner = NULL;
};

bullet::bullet(RLE_SPRITE **dt, BITMAP *dst, const int s_index, const int x,  const int y,  const int z, const int bg_f, const int d, const st_bullet_params &p) : base_image(dt, dst, x, y, z, s_index, bg_f, d)
{
    x_inc = p.x_inc;
    y_inc = p.y_inc;

    x_min = p.x_min;
    x_max = p.x_max;
    y_min = p.y_min;
    y_max = p.y_max;

    owner = NULL;
    score = 0;    

    hit_pixels = p.hit_pixels;
    n_hit_pixels = p.n_hit_pixels;

    colors = p.colors;

    hit_z_order_low = p.hit_z_order_low;
    hit_z_order_high = hit_z_order_high;
};

bullet::bullet(RLE_SPRITE **dt, BITMAP *dst, class player *p_owner, const int s_index, const int x,  const int y,  const int z, const int bg_f, const int d, const st_bullet_params &p) : base_image(dt, dst, x, y, z, s_index, bg_f, d)
{
    x_inc = p.x_inc;
    y_inc = p.y_inc;

    x_min = p.x_min;
    x_max = p.x_max;
    y_min = p.y_min;
    y_max = p.y_max;

    owner = p_owner;  //  May be a NULL pointer if no-owner (enemy bullet)
    score = 0;    

    hit_pixels = p.hit_pixels;
    n_hit_pixels = p.n_hit_pixels;

    colors = p.colors;

    hit_z_order_low = p.hit_z_order_low;
    hit_z_order_high = hit_z_order_high;
    
    if(owner)
      owner->inc_bullet_counter();
};

bullet::bullet(const bullet &other)     // COPY constructor
{
    x_inc = other.x_inc;
    y_inc = other.y_inc;

    x_min = other.x_min;
    x_max = other.x_max;
    y_min = other.y_min;
    y_max = other.y_max;

    owner = NULL;  //  Will not share owner.
    score = 0;    

    hit_pixels = other.hit_pixels;
    n_hit_pixels = other.n_hit_pixels;

    colors = other.colors;

    hit_z_order_low = other.hit_z_order_low;
    hit_z_order_high = other.hit_z_order_high;
};

bullet::~bullet()
{
    if(owner)  //  Tell owner bullet-out-of-scoop.
      owner->update_score(score);
};

int bullet::test_collision_in_list(sprite_list *list, int sprite_index)
{
    if((*list).size())  /*  if list is NOT empty. */
    {
    sprite_list::iterator iter;
    
    iter = (*list).begin();
    do{
        if((*iter)->get_sprite_index() == sprite_index)
          if((collision((*iter)->get_x_pos(), (*iter)->get_y_pos(), *(*game_sprites->images)[sprite_index])))
          {
            if((*iter)->get_sprite_index() == ((*game_sprites).inv_missile))
            { //  Make explosion for Invaders bullets only.
              class explosion *e = new explosion(&(*game_sprites->images)[0], (*iter)->get_dest_bitmap(), (*game_sprites).inv_missile_explosion, (*game_sprites).inv_missile_explosion, (*iter)->get_x_pos()+(((*game_sprites->images)[sprite_index])->w >> 1), (*iter)->get_y_pos()+(((*game_sprites->images)[sprite_index])->h >> 1), EXPLOSION_Z_ORDER, 8, G_TRUE, G_FALSE);
              if(e)
                explosions_list.push_back(e);
            }
            
            (*iter)->set_sprite_index(-1);  //  Will be removed later.
            return G_TRUE;
          }
          
        iter++;
      }while(iter != (*list).end());
    }
    
    return G_FALSE;
}

int bullet::action(void)
{
    int x = get_x_pos();
    int y = get_y_pos();
    int s_index = get_sprite_index();
    int hit;

    if(s_index < 0)
      return G_FALSE;
      
    /*  Test for hit  */
    hit = test_hit_pixels(get_background_bitmap(), colors, 0, 0, hit_pixels, n_hit_pixels);
    if(hit)
    {
      if((hit >= 13) && (hit <=15))
      { //  Bullet hit shield. Damage shield.
        rectfill(get_dest_bitmap(), x-3, y-3, x+3+((*game_sprites->images)[s_index])->w, y+3+((*game_sprites->images)[s_index])->h, 0);
        if(s_index == ((*game_sprites).inv_missile))
        { //  Make explosion for Invaders bullets only.
          class explosion *e = new explosion(&(*game_sprites->images)[0], get_dest_bitmap(), (*game_sprites).inv_missile_explosion, (*game_sprites).inv_missile_explosion, x+(((*game_sprites->images)[s_index])->w >> 1), y+(((*game_sprites->images)[s_index])->h >> 1), EXPLOSION_Z_ORDER, 8, G_TRUE, G_FALSE);
          if(e)
            explosions_list.push_back(e);
        }
        
        set_sprite_index(-1);
        return G_TRUE;
      }
      
      if((hit >= 65) && (hit <=67))
        if(test_collision_in_list(&e_bullets_list, (*game_sprites).inv_missile))
        { //  Bullet hit invader missile.
          set_sprite_index(-1);
          return G_TRUE;
        }
        
      if(invaders)
      {
        if((hit >= 16) && (hit <= 45))
          score = ((formation *) invaders)->test_formation_for_hit(x, y, s_index, (*game_sprites).i_blast, (*game_sprites).i_blast+2, sound_flag & SOUND_INVADERS);

        if((hit >= 32) && (hit <= 34))
        {
          RLE_SPRITE *bullet_sprite = (*game_sprites->images)[s_index];
          score = invaders->destroy_mother_ship(x + ((bullet_sprite->w) >> 1));
        }

      }  
      if(galaxians)
      {
        if((hit >= 46) && (hit <=64))
          score = ((formation *) galaxians)->test_formation_for_hit(x, y, s_index, (*game_sprites).g_blast, (*game_sprites).g_blast+2, sound_flag & SOUND_GALAXY);
      }    
      
      if((hit >= 1) && (hit <= 12))
      {  //  Kill player...
         if(hit < 7)
           player_1->kill(player_2->get_lives());
         else  
           player_2->kill(player_1->get_lives());
      }
      
      set_sprite_index(-1);
      return G_TRUE;
    }
    
    x += x_inc;
    y += y_inc;
    
    if(x > x_max)
      set_sprite_index(-1);
    if(x < x_min)
      set_sprite_index(-1);
    if(y > y_max)
      set_sprite_index(-1);
    if(y < y_min)
      set_sprite_index(-1);

    if(get_sprite_index() == -1)  //  Bullet hit ground. explosion - invaders only.
      if(s_index == ((*game_sprites).inv_missile))
      { //  Make explosion for Invaders bullets only.
        class explosion *e = new explosion(&(*game_sprites->images)[0], get_dest_bitmap(), (*game_sprites).inv_missile_explosion, (*game_sprites).inv_missile_explosion, x+(((*game_sprites->images)[s_index])->w >> 1), y+(((*game_sprites->images)[s_index])->h >> 1), EXPLOSION_Z_ORDER, 8, G_TRUE, G_FALSE);
        if(e)
        explosions_list.push_back(e);
      }
      
    set_pos(x, y, G_FALSE);    
    return G_TRUE;
};
//---------  END    --  CLASS bullet  -----------

//---------  START  --  CLASS player  -----------
player::player() : base_image()
{   // This cannot be used.
    footer_bmp = NULL;
    player_small_sprite = NULL;
    player_hit_pixels = NULL;
    demo_stream  = NULL;
    n_player_hit_pixels = player_killed_by_enemy = 0;
    lives = score = bullets = killed = halftone_counter = last_score_shown = 0;
    player_sprite_index = halftone_sprite_index = -1;
    players++;
    current_bonus_index = current_demo_index = current_frame_counter = 0;
    memset(player_bonus_scores, 0, PLAYER_BONUSES * sizeof(int));
};

player::player(RLE_SPRITE **dt, BITMAP *dst, BITMAP *footer_dst, const int s_index, const int x, const int y, const int z, const int l, const int bg_f, const int d, const st_player_params &p, const int g_font, char *txt_score_h, const int tx_score_h, const int ty_score_h, const int txo_score_h, const int tyo_score_h, char *txt_score, const int tx_score, const int ty_score, const int txo_score, const int tyo_score, char *txt_join, const int tx_join, const int ty_join, const int txo_join, const int tyo_join, const int s_index_small) : base_image(dt, dst, x, y, z, s_index, bg_f, d), text_score_header(dst, txt_score_h, tx_score_h, ty_score_h, g_font, G_TRUE, 246, -1, txo_score_h, tyo_score_h, NULL, G_FALSE), text_score(dst, txt_score, tx_score, ty_score, g_font, G_TRUE, 215, -1, txo_score, tyo_score, NULL, G_FALSE), text_join(footer_dst, txt_join, tx_join, ty_join, g_font, G_FALSE, 251, -1, txo_join, tyo_join, NULL, 18, 6, G_FALSE)
{
    players++;
    lives = l;

    footer_bmp = footer_dst;
    player_small_sprite = (*game_sprites->images)[s_index_small];
    score = bullets = killed = last_score_shown = player_killed_by_enemy = 0;
    start_x = x;
    start_y = y;
    score = bullets = halftone_counter = 0;
    shot_fired = G_FALSE;
    bullets_max = p.bullets_max;
    bullet_sprite_index = p.bullet_sprite_index;
    exp_sprite_index_low = p.exp_sprite_index_low;
    exp_sprite_index_high = p.exp_sprite_index_high;
    
    player_sprite_index = s_index;
    halftone_sprite_index = p.halftone_sprite_index; 
    fire_sample = p.fire_sample;
    exp_sample = p.exp_sample;
    bonus_life_sample = p.bonus_life_sample;
    keys = *p.keys;
    x_min = p.x_min;
    x_max = p.x_max;
    
    player_hit_pixels = p.player_hit_pixels;
    n_player_hit_pixels = p.n_player_hit_pixels;
    
    x_inc = x_speed = p.x_inc;
    
    x_bullet_offset = (dt[s_index]->w >> 1) - ((dt[bullet_sprite_index]->w) >> 1);
    y_bullet_start = y; //  - dt[bullet_sprite_index]->h;
    
    current_demo_index = current_frame_counter = 0;
    demo_stream  = NULL;
    if(p.demo_stream)
    { //  We are running in DEMO_MODE
      demo_stream = (unsigned short *) malloc(p.demo_stream[0] * sizeof(unsigned short));
      if(demo_stream)
      { //  We are running in DEMO_MODE
        memcpy(demo_stream, p.demo_stream, p.demo_stream[0] * sizeof(unsigned short));
        current_demo_index = 1;
        current_frame_counter = GET_FRAME_COUNTER(demo_stream[current_demo_index]);
      }

    }
    current_bonus_index = 0;
    memcpy(player_bonus_scores, p.player_bonus_scores, PLAYER_BONUSES * sizeof(int));
    
    if(lives)
      text_score_header.set_blinking_params(27,9);
};

player::player(const player &other)     // COPY constructor
{
    players++;
    score = other.score; 
    last_score_shown = other.last_score_shown;
    bullets = 0;  //  Do not inherit other player bullets.
    bullets_max = other.bullets_max;
    killed = other.killed;
    lives = other.lives;

    footer_bmp = other.footer_bmp;
    fire_sample = other.fire_sample;
    exp_sample = other.exp_sample;
    bonus_life_sample = other.bonus_life_sample;
    
    player_killed_by_enemy = other.player_killed_by_enemy;
    halftone_counter = other.halftone_counter;
    start_x = other.start_x;
    start_y = other.start_y;

    player_hit_pixels = other.player_hit_pixels;
    n_player_hit_pixels = other.n_player_hit_pixels;

    shot_fired = other.shot_fired;
    bullet_sprite_index = other.bullet_sprite_index;
    exp_sprite_index_low = other.exp_sprite_index_low;
    exp_sprite_index_high = other.exp_sprite_index_high;
    x_bullet_offset = other.x_bullet_offset;
    y_bullet_start = other.y_bullet_start;

    player_sprite_index = other.player_sprite_index;
    halftone_sprite_index = other.halftone_sprite_index; 
    
    keys = other.keys;
    x_min = other.x_min;
    x_max = other.x_max;
    x_speed = other.x_speed;
    x_inc = other.x_inc;
    
    bullet_params = other.bullet_params;

    text_score_header = other.text_score_header;
    text_score = other.text_score;
    text_join = other.text_join;
    player_small_sprite = other.player_small_sprite;
    
    current_bonus_index = other.current_bonus_index;
    memcpy(player_bonus_scores, other.player_bonus_scores, PLAYER_BONUSES * sizeof(int));
    
    current_demo_index = other.current_demo_index;
    current_frame_counter = other.current_frame_counter;
    demo_stream = NULL;
    if(other.demo_stream)
    { //  We are running in DEMO_MODE
      demo_stream = (unsigned short *) malloc(other.demo_stream[0] * sizeof(unsigned short));
      if(demo_stream)
        memcpy(demo_stream, other.demo_stream, other.demo_stream[0] * sizeof(unsigned short));
    }
};

player::~player()
{
    if(demo_stream)
      free(demo_stream);
      
    players--;
};

void player::do_fire(void)
{
    if(formation::get_attack_ok())
    {
      class bullet *p = new bullet(&(*game_sprites->images)[0], get_dest_bitmap(), this, bullet_sprite_index, get_x_pos()+x_bullet_offset, y_bullet_start,  PLAYER_BULLET_Z_ORDER, G_TRUE, G_FALSE, bullet_params);
      if(p)
      {    
        shot_fired = G_TRUE;
        p_bullets_list.push_back(p);
        if(sound_flag)
          play_sample((SAMPLE *) s_data[fire_sample].dat, 255, 128, 1000, 0);
      }  
    }
};

void player::update_score(int value)
{
    bullets--;
    score += value;
    if(score >= 1000000)
      score -= 1000000;

    if(current_bonus_index < PLAYER_BONUSES)
      if(score >= player_bonus_scores[current_bonus_index])
      {
        lives++;
        draw_player_lives();
        current_bonus_index++;
        if(sound_flag)
          play_sample((SAMPLE *) s_data[bonus_life_sample].dat, 224, 128, 1000, 0);
      }
}

int player::p_test_hit_pixels(void)
{
    if(killed)  //  Don't check if new player not on field yet.
      return 0;
      
    player_killed_by_enemy = 0;
    if(halftone_counter)  //  No test in halftone mode. (cannot be hit).
      return 0;
 
    player_killed_by_enemy = test_hit_pixels(get_dest_bitmap(), bullet_hit_colors, get_x_pos(), get_y_pos(), player_hit_pixels, n_player_hit_pixels);
    return 0;
}

int player::kill(int more_players_on_field)
{
    if(killed)  //  Cannot do the kill twice.
      return G_FALSE;
      
    killed = G_TRUE;
    int s_index = get_sprite_index();
    if((s_index < 0) || (lives == 0))
      return G_FALSE;
      
    lives--;
    if(!lives)
      text_score_header.set_blinking_params(0,0);
        
    class explosion *e = new explosion(&(*game_sprites->images)[0], get_dest_bitmap(), exp_sprite_index_low, exp_sprite_index_high, get_x_pos()+(((*game_sprites->images)[s_index])->w >> 1), get_y_pos()+(((*game_sprites->images)[s_index])->h >> 1), EXPLOSION_Z_ORDER, 5, G_TRUE, G_FALSE);
    if(e)
      explosions_list.push_back(e);
    if(sound_flag)
      play_sample((SAMPLE *) s_data[exp_sample].dat, 255, 128, 1000, 0);

    
    if(player_killed_by_enemy)
    { //  Test which enemy killed player.
      int hit_score = ((formation *) galaxians)->test_formation_for_hit(get_x_pos(), get_y_pos(), s_index, (*game_sprites).g_blast, (*game_sprites).g_blast+2, sound_flag & SOUND_GALAXY);
      score += hit_score;
      player_killed_by_enemy = 0;  //  Reset flag.
    }
    
    go_to_start_pos(G_FALSE);
    if(more_players_on_field)  //  Immidiate recover as halftone.
      switch_to_halftone_sprite(250);
    else
      formation::clear_attack_ok();
};

void player::inc_current_demo_index(void)
{
    if(current_frame_counter)
    {
      current_frame_counter--;
      return;
    }

    current_demo_index++;
    if(end_of_demo_input())
      return;

    current_frame_counter = GET_FRAME_COUNTER(demo_stream[current_demo_index]);
}

int player::action(void)
{
// allegro_message("index = <%d> killed = <%d lives = <%d", get_sprite_index(),killed,lives);
    if((get_sprite_index() < 0) || (lives == 0))
      return G_FALSE;
      
    if(halftone_counter)
    {
      halftone_counter--;
      if(halftone_counter > 200)
        return G_TRUE;       //  Player still invisible after kill.
        
      remove_player_lives(); //  Update screen when bringing-up new player.
      killed = G_FALSE;      //  From now on halftone player is visible/active.
      draw_player_lives();

      if(!halftone_counter)  //  Finished halftone-time
        return switch_to_regular_sprite();
    }
      
    if(killed)
      return G_TRUE;

    int fire;
    int x = get_x_pos();
    
    if(demo_stream)
    { //  We are running in DEMO_MODE. Get input from demo stream.
      if(end_of_demo_input())
        return G_TRUE;

      fire = demo_stream[current_demo_index] & BIT_KEY_FIRE;
      if(demo_stream[current_demo_index] & BIT_KEY_LEFT)
        if(x > x_min)
          x -= x_inc;
          
      if(demo_stream[current_demo_index] & BIT_KEY_RIGHT)
        if(x < x_max)
          x += x_inc;
          
      inc_current_demo_index();
    }
    else
    {  //  Get input from player controls.
      fire = key[keys.fire];
    
      if(key[keys.left])
        if(x > x_min)
          x -= x_inc;

      if(key[keys.right])
        if(x < x_max)
          x += x_inc;    
    }
            
    if(fire)
    {
      if(!shot_fired)
        if(bullets < bullets_max)
          do_fire();
    }
    else shot_fired = G_FALSE;      

    if(x < x_min)
      x = x_min;
    if(x > x_max)
      x = x_max;

    set_pos(x,get_y_pos(), G_FALSE);
};

void player::remove_player_text(void)
{
    text_score_header.remove();
    text_score.remove();
    if((lives == 0) || demo_stream)
      text_join.remove();
};

void player::draw_player_text(unsigned char game_over)
{
    if(last_score_shown != score)
    {  // a bit optimization.
      char text[7];
      char t_score[7];
      
      last_score_shown = score;
      memset(text, '0', 7);
      sprintf(t_score,"%d", score);
      strcpy((text+6)-strlen(t_score), t_score);
      text_score.set_text(text);
    }

    text_score_header.draw();
    text_score.draw();
       
    if((lives == 0 && (!game_over)) || demo_stream)
      text_join.draw();  //  Show join-text if game is not over.
};

void player::remove_player_lives(void)
{
    if(demo_stream)
      return;  //  We don't draw them in Demo-mode

    int small_x_pos = 0;
    int x_inc = player_small_sprite->w+2;
    int small_y_pos = (footer_bmp->h) - (player_small_sprite->h);
    int i;
    
    if(start_x)
    { //  Go from left to right.
      small_x_pos = SCREEN_W - (player_small_sprite->w);
      x_inc = -(player_small_sprite->w+2);
    }
      
    for(i = 0; i < lives; i++)
    {
      rectfill(footer_bmp, small_x_pos, small_y_pos,  small_x_pos+(player_small_sprite->w), small_y_pos+(player_small_sprite->h), 0);
      small_x_pos += x_inc;
    }
}

void player::draw_player_lives(void)
{
    if(demo_stream)
      return;  //  We don't draw them in Demo-mode
      
    int small_x_pos = 0;
    int x_inc = player_small_sprite->w+2;
    int small_y_pos = (footer_bmp->h) - (player_small_sprite->h);
    int i;
    
    if(start_x)
    { //  Go from left to right.
      small_x_pos = SCREEN_W - (player_small_sprite->w);
      x_inc = -(player_small_sprite->w+2);
    }

    for(i = 0; i < lives-1; i++)
    {
      draw_rle_sprite(footer_bmp, player_small_sprite, small_x_pos, small_y_pos);
      small_x_pos += x_inc;
    }
}

//---------  END    --  CLASS player  -----------

//---------  START  --  CLASS explosion  --------
explosion::explosion() : base_image(NULL, -1)
{   //  This cannot be used.
    index_low = index_high = -1;  //  invalid speed.
    speed = frame_cnt = 0;
};

explosion::explosion(RLE_SPRITE **dt, BITMAP *dst, const int i_low, const int i_high, const int x, const int y, const int z, const unsigned int f_speed, const int bg_f, const int d) : base_image(dt, dst, x-(((*game_sprites->images)[i_low]->w) >> 1), y-(((*game_sprites->images)[i_low]->h) >> 1), z, i_low, bg_f, d)
{
    center_x = x;
    center_y = y;
    speed = frame_cnt = f_speed;
    index_low = i_low;
    index_high = i_high;
};

explosion::explosion(const explosion &other)
{
    center_x = other.center_x;
    center_y = other.center_y;
    speed = other.speed;
    frame_cnt = other.frame_cnt;
    index_low = other.index_low;
    index_high = other.index_high;
};
explosion::~explosion()
{
};

int explosion::action(void)
{
    RLE_SPRITE *sprite;
    int index = get_sprite_index();

    if(index < 0)
      return G_FALSE;

    if(frame_cnt > 0)
    {
      frame_cnt--;
      return G_TRUE;
    };
    frame_cnt = speed;

    index++;
    if(index > index_high)
    {
      set_sprite_index(-1);
      return G_TRUE;
    }
    else set_sprite_index(index);

    sprite = (*game_sprites->images)[index];

    set_pos((center_x - ((sprite->w) >> 1)), (center_y - ((sprite->h) >> 1)), G_FALSE);
};
//---------  END    --  CLASS explosion  --------

//---------  START  --  CLASS formation  --------
formation::formation()
{
    buffer = NULL;
    items = NULL;
    live_items = 0;
};

formation::formation(const st_formation_param &p)
{
    int i;
    int j;
    st_item *p_item;
    
    buffer = p.buffer;
    frame_rate = frame_counter = p.frame_rate;
    counter = 0;
    items = p.list;
    x_left_border = p.x_left_border;
    x_right_border = p.x_right_border;
    y_top_border = p.y_top_border;
    y_bottom_border = p.y_bottom_border;
    rows = p.rows;
    cols = p.cols;
    
    moves = p.moves;

    current_move = 0;
    move_count =  moves[current_move].times;
      
    live_items = 0; 
    for(j = 0; j < cols; j++)
      for(i = 0; i < rows; i++)
      { //  count live items.
        p_item = &items[j]+(i * cols);
        if(p_item->state != ITEM_IS_DEAD)
          live_items++;
      }
}

#define HANDLE_ITEM \
{ /* Using a MACRO to handle formation-movements */ \
  p = &items[j]+(i * cols); \
  if(p->state != ITEM_IS_DEAD) \
  { \
    p->x += moves[current_move].x_inc;  \
    p->y += moves[current_move].y_inc;  \
    if(p->state == ITEM_IN_FORMATION) \
      { \
        (p->sprite)->set_pos(p->x,p->y, G_FALSE); \
        (p->sprite)->play_anim(); \
      } \
  } \
}


void formation::move_formation_right()
{   //  Run on formation from right to left.
    int i;
    int j;
    st_item *p;
    for(j = cols-1; j >= 0; j--)
      for(i = 0; i < rows; i++)      
       HANDLE_ITEM;
    
    counter++;      //  Counting formation moves for sync.
}

void formation::move_formation_left()
{   //  Run on formation from left to right.
    int i;
    int j;
    st_item *p;
    for(j = 0; j < cols; j++)
      for(i = 0; i < rows; i++)
        HANDLE_ITEM;
    
    counter++;      //  Counting formation moves for sync.
}

void formation::move_formation_down()
{   //  Run on formation from bottom to top.
    int i;
    int j;
    st_item *p;
    for(i = rows-1; i >=0; i--)
      for(j = 0; j < cols; j++)      
        HANDLE_ITEM;
    
    counter++;      //  Counting formation moves for sync.
}

void formation::move_formation_up()
{   //  Run on formation from top to bottom.
    int i;
    int j;
    st_item *p;
    for(i = 0; i < rows; i++)
      for(j = 0; j < cols; j++)      
       HANDLE_ITEM;
    
    counter++;      //  Counting formation moves for sync.
}

int formation::test_formation_right()
{
    int i;
    int j;
    st_item *p;
    for(j = cols-1; j >= 0; j--)
      for(i = 0; i < rows; i++)      
      {
        p = &items[j]+(i * cols);
// allegro_message("row = <%d> col = <%d> state = <%d>",i, j, p->state);        
        if(p->state != ITEM_IS_DEAD)
          if((p->x + (p->sprite->get_sprite())->w) >= x_right_border)
          {
            move_count = 0;  //  clear move count
            return G_TRUE;
          }  
          else
            return G_FALSE;
      }
        
    return G_FALSE;
} 

int formation::test_formation_left()
{
    int i;
    int j;
    st_item *p;
    for(j = 0; j < cols; j++)    
      for(i = 0; i < rows; i++)
      {
        p = &items[j]+(i * cols);
        if(p->state != ITEM_IS_DEAD)
          if(p->x <= x_left_border)
          {
            move_count = 0;  //  clear move count
            return G_TRUE;
          }  
          else
            return G_FALSE;
      }
        
    return G_FALSE;
}

int formation::test_formation_bottom()
{
    int i;
    int j;
    st_item *p;
    for(i = rows-1; i >=0; i--)
      for(j = 0; j < cols; j++)      
      {
        p = &items[j]+(i * cols);
        if(p->state != ITEM_IS_DEAD)
          if((p->y + (p->sprite->get_sprite())->h) >= y_bottom_border)
          {
            move_count = 0;  //  clear move count
            return G_TRUE;
          }  
          else
            return G_FALSE;
      }
        
    return G_FALSE;
}

int formation::test_formation_top()
{
    int i;
    int j;
    st_item *p;
    for(i = 0; i < rows; i++)
      for(j = 0; j < cols; j++)      
      {
        p = &items[j]+(i * cols);
        if(p->state != ITEM_IS_DEAD)
          if(p->y <= y_top_border)
          {
            move_count = 0;  //  clear move count
            return G_TRUE;
          }  
          else
            return G_FALSE;
      }
        
    return G_FALSE;
}

void formation::remove(void)
{
    int i, j;
    st_item *p;

    for(j = 0; j < cols; j++)
      for(i = 0; i < rows; i++)      
      {
        p = &items[j]+(i * cols);
        if(p->state == ITEM_IN_FORMATION)
          p->sprite->remove();
      }
}

void formation::draw(void)
{
    int i, j;
    st_item *p;

    for(j = 0; j < cols; j++)
      for(i = 0; i < rows; i++)      
      {
        p = &items[j]+(i * cols);
        if(p->state == ITEM_IN_FORMATION)
          p->sprite->draw();
      }
}

int formation::test_formation_for_hit(int x_pos, int y_pos, int bullet_index, int e_index_low, int e_index_high, unsigned int do_sound)
{
    int i, j;
    int s_index;
    st_item *p;

    for(j = 0; j < cols; j++)
      for(i = rows-1; i >=0; i--)
      {
        p = &items[j]+(i * cols);
        if(p->state != ITEM_IS_DEAD)
          if(p->sprite->collision(x_pos, y_pos, *(*game_sprites->images)[bullet_index]))
          { //  Kill this item
            s_index = p->sprite->get_sprite_index();
            int t_score = p->score;
            if(p->state != ITEM_IN_FORMATION)  //  Double score in attack.
              t_score <<= 1;
            class explosion *e = new explosion(&(*game_sprites->images)[0], p->sprite->get_dest_bitmap(), e_index_low, e_index_high, p->sprite->get_x_pos()+(((*game_sprites->images)[s_index])->w >> 1), p->sprite->get_y_pos()+(((*game_sprites->images)[s_index])->h >> 1), EXPLOSION_Z_ORDER, 5, G_TRUE, G_FALSE);
            if(e)
              explosions_list.push_back(e);

            if(t_score >= 200)
            { //  Add bonus-score sprite to explosion list.
              int t_bonus_index = (*game_sprites).gal_bonus_200;
              if(t_score == 400)
                t_bonus_index = (*game_sprites).gal_bonus_400;
              if(t_score == 800)
                t_bonus_index = (*game_sprites).gal_bonus_800;
              e = new explosion(&(*game_sprites->images)[0], p->sprite->get_dest_bitmap(), t_bonus_index, t_bonus_index+2, p->sprite->get_x_pos()+(((*game_sprites->images)[s_index])->w >> 1), p->sprite->get_y_pos()+(((*game_sprites->images)[s_index])->h >> 1), EXPLOSION_Z_ORDER, 14, G_TRUE, G_FALSE);
              if(e)
                explosions_list.push_back(e);
            }

                        
            if(do_sound)  //  Ganenrate explosion sound.
              play_sample((SAMPLE *) s_data[p->sample].dat, 255, 128, 1000, 0);


            delete (p->sprite);
            p->sprite = NULL;
            p->state = ITEM_IS_DEAD;
            live_items--;

            return (t_score);
          }
      }
         
      return 0;
}

void formation::get_fire_options(st_pixel *opt, int max_col, int *n_opt)
{
    int i, j;
    st_item *p;
    int lowest_fire_line = GAME_FOOTER_BORDER_LINE-60;

    *n_opt = 0;
    for(j = 0; j < cols; j++)
      for(i = rows-1; i >=0; i--)
      {
        p = &items[j]+(i * cols);
        if(p->state == ITEM_IN_FORMATION)
        {
          if(p->sprite->get_y_pos() > lowest_fire_line)
            break;  //  Don't use this row for fire.
          opt[*n_opt].x = p->sprite->get_x_pos() + ((*game_sprites->images)[p->sprite->get_sprite_index()]->w >> 1);
          opt[*n_opt].y = p->sprite->get_y_pos() + (*game_sprites->images)[p->sprite->get_sprite_index()]->h;
          (*n_opt)++;
          if(*n_opt == max_col)
            return;
          else
            break;  
        }  
      }
}

void formation::action()
{
    if(!live_items)
      return;
      
    frame_counter--;
    if(frame_counter)
      return;
    
    frame_counter = frame_rate;

    //  Finished moving the whole formation, test boundaries.
    if(moves[current_move].x_inc > 0) //  if we go to the right.
      test_formation_right();
    if(moves[current_move].x_inc < 0) //  if we go to the left.
      test_formation_left();
    if(moves[current_move].y_inc > 0) //  if we go to the down.
      test_formation_bottom();
    if(moves[current_move].y_inc < 0) //  if we go to the up.
      test_formation_top();

    if(move_count == 0)
    {
      current_move += moves[current_move].int_to_next_move;
      move_count =  moves[current_move].times;
      return;
    }
      
    if(moves[current_move].x_inc > 0)
      move_formation_right();
    if(moves[current_move].x_inc < 0)
      move_formation_left();
    if(moves[current_move].y_inc > 0)
      move_formation_down();
    if(moves[current_move].y_inc < 0)
      move_formation_up();

    move_count--;
}

formation::~formation()
{
  for(int i = 0; i < rows*cols; i++)
    if(items[i].sprite)
      delete(items[i].sprite);
      
  delete [] items;    
  delete [] moves;
};
//---------  END    --  CLASS formation  --------