#include "Monster.h"

Monster monsters[256];
LiveMonster live_monsters[256];
int live_monster_count;
int move_monsters;

int getLiveMonsterFace(LiveMonster * live);

SDL_Rect extended_screen_rect;

void
defaultMoveMonster(LiveMonster * live)
{
  // no-op
}

void
defaultDrawMonster(SDL_Rect * pos, LiveMonster * live, SDL_Surface * surface,
                   SDL_Surface * img)
{
  SDL_BlitSurface(img, NULL, surface, pos);
}

void
defaultBreedMonster(LiveMonster * live, SDL_Rect * pos)
{
  // no-op
}

void
initMonsterPos(Position * pos, LiveMonster * live)
{
  pos->pos_x = live->pos_x;
  pos->pos_y = live->pos_y;
  pos->pixel_x = live->pixel_x;
  pos->pixel_y = live->pixel_y;
  pos->w = images[live->monster->image_index[0]]->image->w / TILE_W;
  pos->h = images[live->monster->image_index[0]]->image->h / TILE_H;
}

int
stepMonsterLeft(LiveMonster * live, int float_ok)
{
  Position pos;
  int fail = 0;
  LiveMonster old;
  memcpy(&old, live, sizeof(LiveMonster));
  live->pixel_x -= live->speed_x + map.max_speed_boost;
  if(live->pixel_x < 0) {
    live->pos_x--;
    live->pixel_x = TILE_W + live->pixel_x;
    if(live->pos_x < 0) {
      fail = 1;
    }
  }
  // collision detection
  if(!fail) {
    initMonsterPos(&pos, live);
    if(containsType(&pos, TYPE_WALL | TYPE_DOOR)
       || !(float_ok || onSolidGround(&pos)))
      fail = 1;
  }
  if(fail) {
    memcpy(live, &old, sizeof(LiveMonster));
    return 0;
  }
  return 1;
}

int
stepMonsterUp(LiveMonster * live)
{
  Position pos;
  int fail = 0;
  LiveMonster old;
  memcpy(&old, live, sizeof(LiveMonster));
  live->pixel_y -= live->speed_y + map.max_speed_boost;
  if(live->pixel_y < 0) {
    live->pos_y--;
    live->pixel_y = TILE_H + live->pixel_y;
    if(live->pos_y < 0) {
      fail = 1;
    }
  }
  // collision detection
  if(!fail) {
    initMonsterPos(&pos, live);
    if(containsType(&pos, TYPE_WALL | TYPE_DOOR))
      fail = 1;
  }
  if(fail) {
    memcpy(live, &old, sizeof(LiveMonster));
    return 0;
  }
  return 1;
}

int
stepMonsterRight(LiveMonster * live, int float_ok)
{
  Position pos;
  int fail = 0;
  LiveMonster old;
  memcpy(&old, live, sizeof(LiveMonster));
  live->pixel_x += live->speed_x + map.max_speed_boost;
  if(live->pixel_x >= TILE_W) {
    live->pos_x++;
    live->pixel_x = live->pixel_x - TILE_W;
    if(live->pos_x >= map.w) {
      fail = 1;
    }
  }
  // collision detection
  if(!fail) {
    initMonsterPos(&pos, live);
    if(containsType(&pos, TYPE_WALL | TYPE_DOOR)
       || !(float_ok || onSolidGround(&pos)))
      fail = 1;
  }
  if(fail) {
    memcpy(live, &old, sizeof(LiveMonster));
    return 0;
  }
  return 1;
}

int
stepMonsterDown(LiveMonster * live)
{
  Position pos;
  int fail = 0;
  LiveMonster old;

  memcpy(&old, live, sizeof(LiveMonster));

  live->pixel_y += live->speed_y + map.max_speed_boost;
  if(live->pixel_y >= TILE_H) {
    live->pos_y++;
    live->pixel_y = live->pixel_y - TILE_H;
    if(live->pos_y >= map.h)
      fail = 1;
  }
  // collision detection
  if(!fail) {
    initMonsterPos(&pos, live);
    if(containsType(&pos, TYPE_WALL | TYPE_DOOR))
      fail = 1;
  }

  if(fail) {
    memcpy(live, &old, sizeof(LiveMonster));
    return 0;
  }
  return 1;
}

void
moveDemon(LiveMonster * live_monster)
{
  int j;

  // move sideways until you hit a wall or an edge
  j = 1 + (int) (10.0 * rand() / (RAND_MAX));
  if(j > 7) {
    // increment the face to display
    live_monster->face++;
    if(live_monster->face >=
       live_monster->monster->image_count * live_monster->monster->face_mod)
      live_monster->face = 0;

    if(live_monster->dir == DIR_LEFT) {
      if(!stepMonsterLeft(live_monster, 0)) {
        live_monster->dir = DIR_RIGHT;
      }
    } else {
      if(!stepMonsterRight(live_monster, 0)) {
        live_monster->dir = DIR_LEFT;
      }
    }
  }
}

void
movePlatform(LiveMonster * live_monster)
{
  // increment the face to display
  live_monster->face++;
  if(live_monster->face >=
     live_monster->monster->image_count * live_monster->monster->face_mod)
    live_monster->face = 0;

  // move sideways until you hit a wall or an edge
  if(live_monster->dir == DIR_LEFT) {
    if(!stepMonsterLeft(live_monster, 1)) {
      live_monster->dir = DIR_RIGHT;
    }
  } else {
    if(!stepMonsterRight(live_monster, 1)) {
      live_monster->dir = DIR_LEFT;
    }
  }
}

void
movePlatform2(LiveMonster * live_monster)
{
  // increment the face to display
  live_monster->face++;
  if(live_monster->face >=
     live_monster->monster->image_count * live_monster->monster->face_mod)
    live_monster->face = 0;

  // move up and down
  if(live_monster->dir == DIR_DOWN) {
    if(!stepMonsterDown(live_monster)) {
      live_monster->dir = DIR_UP;
    }
  } else {
    if(!stepMonsterUp(live_monster)) {
      live_monster->dir = DIR_DOWN;
    }
  }
}

void
moveEndGame(LiveMonster * live_monster)
{
  int *p;
  p = (int *) (live_monster->custom);
  // if our custom int is set
  if(*p > 0) {
    // move up and down
    if(live_monster->dir == DIR_DOWN) {
      if(!stepMonsterDown(live_monster)) {
        live_monster->dir = DIR_UP;
      }
    } else {
      if(!stepMonsterUp(live_monster)) {
        live_monster->dir = DIR_DOWN;
      } else {
        if((*p)++ >= 2) {
          *p = 1;
          live_monster->dir = DIR_DOWN;
        }
      }
    }
  }
}

void
moveCrab(LiveMonster * live_monster)
{
  // increment the face to display
  live_monster->face++;
  if(live_monster->face >=
     live_monster->monster->image_count * live_monster->monster->face_mod)
    live_monster->face = 0;

  // move sideways until you hit a wall or an edge
  if(live_monster->dir == DIR_LEFT) {
    if(!stepMonsterLeft(live_monster, 0)) {
      live_monster->dir = DIR_RIGHT;
    }
  } else {
    if(!stepMonsterRight(live_monster, 0)) {
      live_monster->dir = DIR_LEFT;
    }
  }
}

void
moveGhost(LiveMonster * live_monster)
{
  int j;
  // increment the face to display
  j = (int) (10.0 * rand() / (RAND_MAX));
  if(!j) {
    live_monster->face++;
    if(live_monster->face >=
       live_monster->monster->image_count * live_monster->monster->face_mod)
      live_monster->face = 0;
  }
  // move up/down until you hit a wall or an edge
  if(live_monster->dir == DIR_UP) {
    if(!stepMonsterUp(live_monster)) {
      live_monster->dir = DIR_DOWN;
    }
  } else {
    if(!stepMonsterDown(live_monster)) {
      live_monster->dir = DIR_UP;
    }
  }
}

void
moveBat(LiveMonster * live_monster)
{
  int j;

  // increment the face to display
  live_monster->face++;
  if(live_monster->face >=
     live_monster->monster->image_count * live_monster->monster->face_mod)
    live_monster->face = 0;

  if(live_monster->dir == DIR_UPDATE) {
    j = (int) (30.0 * rand() / (RAND_MAX));
    if(j == 0) {
      live_monster->dir = DIR_LEFT;
      if(!stepMonsterLeft(live_monster, 1)) {
        live_monster->dir = DIR_RIGHT;
        stepMonsterRight(live_monster, 1);
      }
    }
  } else {
    // move sideways until you hit a wall or an edge
    if(live_monster->pos_y % 6 < 3)
      stepMonsterDown(live_monster);
    else
      stepMonsterUp(live_monster);

    if(live_monster->dir == DIR_LEFT) {
      if(!stepMonsterLeft(live_monster, 1)) {
        live_monster->dir = DIR_UPDATE;
      }
    } else {
      if(!stepMonsterRight(live_monster, 1)) {
        live_monster->dir = DIR_UPDATE;
      }
    }
  }
}

void
breedBullet(LiveMonster * live, SDL_Rect * pos)
{
  addLiveMonsterChangeMap(MONSTER_BULLET, img_bullet[0], live->pos_x + 2,
                          live->pos_y, 0);
  live_monsters[live_monster_count - 1].pixel_x = 10;
  live_monsters[live_monster_count - 1].dir = DIR_RIGHT;
  live_monsters[live_monster_count - 1].parent = live;
  live->child_count++;
}

void
breedBullet2(LiveMonster * live, SDL_Rect * pos)
{
  addLiveMonsterChangeMap(MONSTER_BULLET, img_bullet[0], live->pos_x - 1,
                          live->pos_y, 0);
  live_monsters[live_monster_count - 1].dir = DIR_LEFT;
  live_monsters[live_monster_count - 1].parent = live;
  live->child_count++;
}

void
moveBullet(LiveMonster * live_monster)
{
  // increment the face to display
  live_monster->face++;
  if(live_monster->face >=
     live_monster->monster->image_count * live_monster->monster->face_mod)
    live_monster->face = 0;

  // move sideways until you hit a wall
  if(live_monster->dir == DIR_LEFT) {
    if(!stepMonsterLeft(live_monster, 1)) {
      live_monster->remove_me = 1;
    }
  } else {
    if(!stepMonsterRight(live_monster, 1)) {
      live_monster->remove_me = 1;
    }
  }
}

void
moveArrow(LiveMonster * live_monster)
{
  // increment the face to display
  int old = getLiveMonsterFace(live_monster);
  int face;
  int n = (int) (100.0 * rand() / (RAND_MAX + 1.0));
  if(n > 0)
    return;

  live_monster->face++;
  if(live_monster->face >=
     live_monster->monster->image_count * live_monster->monster->face_mod) {
    live_monster->face = 0;
  }
  face = getLiveMonsterFace(live_monster);
  if(old < face) {
    live_monster->pos_y--;
  } else if(old > face) {
    live_monster->pos_y++;
  }
}

void
moveTorch(LiveMonster * live_monster)
{
  // increment the face to display
  live_monster->face++;
  if(live_monster->face >=
     live_monster->monster->image_count * live_monster->monster->face_mod)
    live_monster->face = 0;
}

void
moveBear(LiveMonster * live_monster)
{
  // increment the face to display
  int n = live_monster->monster->image_count / 2;
  live_monster->face++;
  // move sideways until you hit a wall or an edge
  if(live_monster->dir == DIR_LEFT) {
    if(live_monster->face >= n * live_monster->monster->face_mod)
      live_monster->face = 0;
    if(!stepMonsterLeft(live_monster, 0)) {
      live_monster->dir = DIR_RIGHT;
      live_monster->face = 0;
    }
  } else {
    if(live_monster->face >=
       live_monster->monster->image_count * live_monster->monster->face_mod)
      live_monster->face = n * live_monster->monster->face_mod;
    if(!stepMonsterRight(live_monster, 0)) {
      live_monster->dir = DIR_LEFT;
      live_monster->face = n * live_monster->monster->face_mod;
    }
  }
}

void
moveFire(LiveMonster * live_monster)
{
  // move up and down until you hit an edge
  if(live_monster->dir == DIR_DOWN) {
    if(!stepMonsterDown(live_monster)) {
      live_monster->dir = DIR_UP;
      live_monster->speed_y =
        live_monster->monster->start_speed_y +
        ((int) ((MAX_RANDOM_SPEED / 2) * rand() / (RAND_MAX)));
      if(live_monster->speed_y <= 0)
        live_monster->speed_y = 1;
    }
  } else {
    if(!stepMonsterUp(live_monster)) {
      live_monster->dir = DIR_DOWN;
      live_monster->speed_y =
        live_monster->monster->start_speed_y +
        ((int) ((MAX_RANDOM_SPEED / 2) * rand() / (RAND_MAX)));
      if(live_monster->speed_y <= 0)
        live_monster->speed_y = 1;
    }
  }
}

void
drawFire(SDL_Rect * pos, LiveMonster * live, SDL_Surface * surface,
         SDL_Surface * img)
{
  int y, original;
  SDL_Rect p, q;
  Position position;
  int index;

  p.x = pos->x;
  //  p.y = (pos->y / TILE_H) * TILE_H - TILE_H;
  original = y = p.y = pos->y;
  p.w = pos->w;
  p.h = pos->h;

  position.pos_x = live->pos_x;
  position.pos_y = live->pos_y;
  position.pixel_x = live->pixel_x;
  position.pixel_y = live->pixel_y;
  position.w = p.w / TILE_W;
  position.h = p.h / TILE_H;

  while(position.pos_y < map.h &&
        !containsType(&position, TYPE_WALL | TYPE_DOOR)) {
    index = (int) ((double) (live->monster->image_count) * rand() / RAND_MAX);
    SDL_BlitSurface(images[live->monster->image_index[index]]->image, NULL,
                    surface, &p);
    p.x = pos->x;
    y += TILE_H;
    p.y = y;
    p.w = pos->w;
    p.h = pos->h;
    position.pos_y++;
  }

  // save the fire column's height in the custom field
  *((int *) (live->custom)) = y - original;

  // draw the last one
  if(position.pos_y > live->pos_y && live->pixel_y) {
    index = (int) ((double) (live->monster->image_count) * rand() / RAND_MAX);
    q.x = 0;
    q.y = 0;
    q.w = images[live->monster->image_index[index]]->image->w;
    q.h = images[live->monster->image_index[index]]->image->h - live->pixel_y;
    SDL_BlitSurface(images[live->monster->image_index[index]]->image, &q,
                    surface, &p);
  }
}

void
moveSmasher(LiveMonster * live_monster)
{
  // move up and down until you hit an edge
  if(live_monster->dir == DIR_DOWN) {
    if(!stepMonsterDown(live_monster)) {
      live_monster->dir = DIR_UP;
      live_monster->speed_y =
        live_monster->monster->start_speed_y / 2 +
        ((int) ((MAX_RANDOM_SPEED / 2) * rand() / (RAND_MAX)));
      if(live_monster->speed_y <= 0)
        live_monster->speed_y = 1;
    }
  } else {
    if(!stepMonsterUp(live_monster)) {
      live_monster->dir = DIR_DOWN;
      live_monster->speed_y =
        live_monster->monster->start_speed_y +
        ((int) ((MAX_RANDOM_SPEED / 2) * rand() / (RAND_MAX)));
      if(live_monster->speed_y <= 0)
        live_monster->speed_y = 1;
    }
  }
}

void
drawSmasher(SDL_Rect * pos, LiveMonster * live, SDL_Surface * surface,
            SDL_Surface * img)
{
  int y = 0;
  SDL_Rect p, q;
  Position position;
  int first_image, first, second;
  int skip;

  first_image = live->monster->image_index[0];
  first = (first_image == img_smash || first_image == img_smash2 ? img_smash :
           (first_image == img_smash3
            || first_image == img_smash4 ? img_smash3 : img_spider));
  second =
    (first ==
     img_smash ? img_smash2 : (first ==
                               img_smash3 ? img_smash4 : img_spider2));

  p.x = pos->x;
  //  p.y = (pos->y / TILE_H) * TILE_H - TILE_H;
  p.y = pos->y - TILE_H;
  p.w = pos->w;
  p.h = pos->h;

  position.pos_x = live->pos_x;
  position.pos_y = live->pos_y - 1;
  position.pixel_x = 0;
  position.pixel_y = 0;
  position.w = p.w / TILE_W;
  position.h = p.h / TILE_H;

  skip = 0;
  while(position.pos_y >= 0 &&
        !containsType(&position, TYPE_WALL | TYPE_DOOR)) {
    SDL_BlitSurface(images[second]->image, NULL, surface, &p);
    // HACK part 1: if p->y is reset to 0 the image was cropped.
    y = p.y;
    if(!y) {
      skip = 1;
      break;
    }
    p.x = pos->x;
    p.y -= TILE_H;
    p.w = pos->w;
    p.h = pos->h;
    position.pos_y--;
  }
  // draw the top one.
  // HACK part 2: if y is 0 the image was cropped, don't draw
  //  if(y && live->pixel_y) {
  if(!skip && live->pixel_y != 0) {
    p.x = pos->x;
    p.y += TILE_H;
    p.y -= live->pixel_y;
    p.w = pos->w;
    p.h = pos->h;

    q.x = 0;
    q.y = TILE_H - live->pixel_y;
    q.w = p.w;
    q.h = images[second]->image->h - q.y;
    SDL_BlitSurface(images[second]->image, &q, surface, &p);
  }

  SDL_BlitSurface(images[first]->image, NULL, surface, pos);
}

int
defaultDetectMonster(Position * pos, LiveMonster * live)
{
  SDL_Rect monster, check;
  SDL_Surface *img;

  // convert pos to pixels
  check.x = pos->pos_x * TILE_W + pos->pixel_x;
  check.y = pos->pos_y * TILE_H + pos->pixel_y;
  check.w = pos->w * TILE_W;
  check.h = pos->h * TILE_H;

  // get the live monster's pixel rect.
  img = images[live->monster->image_index[getLiveMonsterFace(live)]]->image;
  monster.x = live->pos_x * TILE_W + live->pixel_x;
  monster.y = live->pos_y * TILE_H + live->pixel_y;
  monster.w = img->w;
  monster.h = img->h;

  // compare
  return intersectsBy(&check, &monster,
                      (live->monster->harmless ? 1 : MONSTER_COLLISION_FUZZ));
}

int
detectFire(Position * pos, LiveMonster * live)
{
  SDL_Rect monster, check;

  // convert pos to pixels
  check.x = pos->pos_x * TILE_W + pos->pixel_x;
  check.y = pos->pos_y * TILE_H + pos->pixel_y;
  check.w = pos->w * TILE_W;
  check.h = pos->h * TILE_H;

  // get the live monster's pixel rect.
  //  img = images[live->monster->image_index[getLiveMonsterFace(live)]]->image;
  monster.x = live->pos_x * TILE_W + live->pixel_x;
  monster.y = live->pos_y * TILE_H + live->pixel_y;
  monster.w = TILE_W;
  monster.h = *((int *) (live->custom));

  // compare
  return intersectsBy(&check, &monster,
                      (live->monster->harmless ? 1 : MONSTER_COLLISION_FUZZ));
}

void
allocFireCustom(LiveMonster * live)
{
//warning: use of cast expressions as lvalues is deprecated
//if(((int *) live->custom = (int *) malloc(sizeof(int))) == NULL) {
  if((live->custom = (int *) malloc(sizeof(int))) == NULL) {
    fprintf(stderr,
            "Out of memory when trying to allocate custom storage for fire column.\n");
  }
}

void
allocEndGameCustom(LiveMonster * live)
{
//if(((int *) live->custom = (int *) malloc(sizeof(int))) == NULL) {
  if((live->custom = (int *) malloc(sizeof(int))) == NULL) {
    fprintf(stderr,
            "Out of memory when trying to allocate custom storage for end game.\n");
  }
  *((int *) (live->custom)) = 0;
}

/**
   Remember here, images are not yet initialized!
 */
void
initMonsters()
{
  int i;

  move_monsters = 1;
  live_monster_count = 0;

  // init the screen rectangle.
  extended_screen_rect.x = -MONSTER_EXTRA_X * TILE_W;
  extended_screen_rect.y = -MONSTER_EXTRA_Y * TILE_H;
  extended_screen_rect.w = screen->w + 2 * MONSTER_EXTRA_X * TILE_W;
  extended_screen_rect.h = screen->h + 2 * MONSTER_EXTRA_Y * TILE_H;

  // common properties.
  for(i = 0; i < MONSTER_COUNT; i++) {
    monsters[i].start_speed_x = 2;
    monsters[i].start_speed_y = 2;
    monsters[i].image_count = 0;
    monsters[i].moveMonster = defaultMoveMonster;
    monsters[i].drawMonster = defaultDrawMonster;
    monsters[i].face_mod = 1;
    monsters[i].type = i;
    monsters[i].harmless = 0;
    monsters[i].random_speed = 1;
    monsters[i].detectMonster = defaultDetectMonster;
    monsters[i].allocCustom = NULL;
    monsters[i].breeds = NULL;
    monsters[i].breedMonster = defaultBreedMonster;
    monsters[i].damage = 1;
  }

  // crab monster
  strcpy(monsters[MONSTER_CRAB].name, "dungenous crab");
  monsters[MONSTER_CRAB].moveMonster = moveCrab;
  monsters[MONSTER_CRAB].start_speed_x = 2;
  monsters[MONSTER_CRAB].start_speed_y = 2;
  // animation 2x slower
  monsters[MONSTER_CRAB].face_mod = 2;
  monsters[MONSTER_CRAB].random_speed = 0;

  // smasher monster
  strcpy(monsters[MONSTER_SMASHER].name, "smasher");
  monsters[MONSTER_SMASHER].moveMonster = moveSmasher;
  monsters[MONSTER_SMASHER].drawMonster = drawSmasher;
  monsters[MONSTER_SMASHER].start_speed_x = 4;
  monsters[MONSTER_SMASHER].start_speed_y = 4;
  monsters[MONSTER_SMASHER].damage = 2;

  // purple smasher
  strcpy(monsters[MONSTER_SMASHER2].name, "cursed smasher");
  monsters[MONSTER_SMASHER2].moveMonster = moveSmasher;
  monsters[MONSTER_SMASHER2].drawMonster = drawSmasher;
  monsters[MONSTER_SMASHER2].start_speed_x = 4;
  monsters[MONSTER_SMASHER2].start_speed_y = 4;
  monsters[MONSTER_SMASHER2].damage = 4;

  // demon monster
  strcpy(monsters[MONSTER_DEMON].name, "little demon");
  monsters[MONSTER_DEMON].moveMonster = moveDemon;
  monsters[MONSTER_DEMON].start_speed_x = 4;
  monsters[MONSTER_DEMON].start_speed_y = 4;
  // animation 2x slower
  monsters[MONSTER_DEMON].face_mod = 4;
  monsters[MONSTER_DEMON].damage = 2;

  // platforms
  strcpy(monsters[MONSTER_PLATFORM].name, "platform");
  monsters[MONSTER_PLATFORM].moveMonster = movePlatform;
  monsters[MONSTER_PLATFORM].start_speed_x = 4;
  monsters[MONSTER_PLATFORM].start_speed_y = 4;
  monsters[MONSTER_PLATFORM].harmless = 1;

  // platform2
  strcpy(monsters[MONSTER_PLATFORM2].name, "platform2");
  monsters[MONSTER_PLATFORM2].moveMonster = movePlatform2;
  monsters[MONSTER_PLATFORM2].start_speed_x = 4;
  monsters[MONSTER_PLATFORM2].start_speed_y = 4;
  monsters[MONSTER_PLATFORM2].harmless = 1;

  // spider
  strcpy(monsters[MONSTER_SPIDER].name, "patient spider");
  monsters[MONSTER_SPIDER].moveMonster = moveSmasher;
  monsters[MONSTER_SPIDER].drawMonster = drawSmasher;
  monsters[MONSTER_SPIDER].start_speed_x = 2;
  monsters[MONSTER_SPIDER].start_speed_y = 2;
  monsters[MONSTER_SPIDER].damage = 5;

  // bear monster
  strcpy(monsters[MONSTER_BEAR].name, "arctic cave bear");
  monsters[MONSTER_BEAR].moveMonster = moveBear;
  monsters[MONSTER_BEAR].start_speed_x = 1;
  monsters[MONSTER_BEAR].start_speed_y = 1;
  monsters[MONSTER_BEAR].face_mod = 8;
  monsters[MONSTER_BEAR].random_speed = 0;
  monsters[MONSTER_BEAR].damage = 7;

  // torch
  strcpy(monsters[MONSTER_TORCH].name, "torch");
  monsters[MONSTER_TORCH].moveMonster = moveTorch;
  monsters[MONSTER_TORCH].start_speed_x = 1;
  monsters[MONSTER_TORCH].start_speed_y = 1;
  monsters[MONSTER_TORCH].face_mod = 6;
  monsters[MONSTER_TORCH].harmless = 1;

  // arrow trap
  strcpy(monsters[MONSTER_ARROW].name, "arrow trap");
  monsters[MONSTER_ARROW].moveMonster = moveArrow;
  monsters[MONSTER_ARROW].start_speed_x = 1;
  monsters[MONSTER_ARROW].start_speed_y = 1;
  monsters[MONSTER_ARROW].face_mod = 1;
  monsters[MONSTER_ARROW].damage = 1;

  // fire
  strcpy(monsters[MONSTER_FIRE].name, "fire trap");
  monsters[MONSTER_FIRE].moveMonster = moveFire;
  monsters[MONSTER_FIRE].drawMonster = drawFire;
  monsters[MONSTER_FIRE].start_speed_x = 2;
  monsters[MONSTER_FIRE].start_speed_y = 2;
  monsters[MONSTER_FIRE].face_mod = 3;
  monsters[MONSTER_FIRE].detectMonster = detectFire;
  monsters[MONSTER_FIRE].allocCustom = allocFireCustom;
  monsters[MONSTER_FIRE].damage = 4;

  // star
  strcpy(monsters[MONSTER_STAR].name, "star");
  monsters[MONSTER_STAR].moveMonster = moveTorch; // re-use moveTorch; not a bug
  monsters[MONSTER_STAR].start_speed_x = 1;
  monsters[MONSTER_STAR].start_speed_y = 1;
  monsters[MONSTER_STAR].face_mod = 4;
  monsters[MONSTER_STAR].harmless = 1;

  // bullet
  strcpy(monsters[MONSTER_BULLET].name, "curious cannonball");
  monsters[MONSTER_BULLET].moveMonster = moveBullet;
  monsters[MONSTER_BULLET].start_speed_x = 8;
  monsters[MONSTER_BULLET].start_speed_y = 4;
  monsters[MONSTER_BULLET].face_mod = 1;
  monsters[MONSTER_BULLET].random_speed = 0;
  monsters[MONSTER_BULLET].damage = 10;

  // cannon
  strcpy(monsters[MONSTER_CANNON].name, "cannon");
  monsters[MONSTER_CANNON].harmless = 1;
  monsters[MONSTER_CANNON].breeds = &monsters[MONSTER_BULLET];
  monsters[MONSTER_CANNON].breedMonster = breedBullet;
  monsters[MONSTER_CANNON].max_children = 1;  // 1 bullet active at a time

  // cannon2
  strcpy(monsters[MONSTER_CANNON2].name, "cannon");
  monsters[MONSTER_CANNON2].harmless = 1;
  monsters[MONSTER_CANNON2].breeds = &monsters[MONSTER_BULLET];
  monsters[MONSTER_CANNON2].breedMonster = breedBullet2;
  monsters[MONSTER_CANNON2].max_children = 1; // 1 bullet active at a time

  // bat
  strcpy(monsters[MONSTER_BAT].name, "vampire bat");
  monsters[MONSTER_BAT].moveMonster = moveBat;
  monsters[MONSTER_BAT].start_speed_x = 6;
  monsters[MONSTER_BAT].start_speed_y = 4;
  monsters[MONSTER_BAT].face_mod = 3;
  monsters[MONSTER_BAT].damage = 3;

  // ghost
  strcpy(monsters[MONSTER_GHOST].name, "entombed spirit");
  monsters[MONSTER_GHOST].moveMonster = moveGhost;
  monsters[MONSTER_GHOST].start_speed_y = 2;
  monsters[MONSTER_GHOST].damage = 2;
  monsters[MONSTER_GHOST].face_mod = 4;
  monsters[MONSTER_GHOST].random_speed = 0;

  // end game
  strcpy(monsters[MONSTER_END_GAME].name, "lost friend!");
  monsters[MONSTER_END_GAME].harmless = 1;
  monsters[MONSTER_END_GAME].moveMonster = moveEndGame;
  monsters[MONSTER_END_GAME].start_speed_y = 3;
  monsters[MONSTER_END_GAME].allocCustom = allocEndGameCustom;
  monsters[MONSTER_END_GAME].random_speed = 0;

  // add additional monsters here

  for(i = 0; i < MONSTER_COUNT; i++) {
    fprintf(stderr, "Added monster: %s.\n", monsters[i].name);
  }
  fflush(stderr);
}

void
resetMonsters()
{
  live_monster_count = 0;
}

void
addMonsterImage(int monster_index, int image_index)
{
  monsters[monster_index].image_index[monsters[monster_index].image_count++] =
    image_index;
  fprintf(stderr, "monster image added. monster=%d image_count=%d\n",
          monster_index, monsters[monster_index].image_count);
  fflush(stderr);
}

int
isMonsterImage(int image_index)
{
  // new fast way of doing this.
  if(image_index == EMPTY_MAP)
    return -1;
  return images[image_index]->monster_index;
}

void
addLiveMonster(int monster_index, int image_index, int x, int y)
{
  addLiveMonsterChangeMap(monster_index, image_index, x, y, 1);
}

void
addLiveMonsterChangeMap(int monster_index, int image_index, int x, int y,
                        int change_map)
{
  int i;
  Monster *m = &monsters[monster_index];
  live_monsters[live_monster_count].parent = NULL;
  live_monsters[live_monster_count].pos_x = x;
  live_monsters[live_monster_count].pos_y = y;
  live_monsters[live_monster_count].pixel_x = 0;
  live_monsters[live_monster_count].pixel_y = 0;
  if(m->random_speed) {
    live_monsters[live_monster_count].speed_x =
      m->start_speed_x + ((int) (MAX_RANDOM_SPEED * rand() / (RAND_MAX)));
    live_monsters[live_monster_count].speed_y =
      m->start_speed_y + ((int) (MAX_RANDOM_SPEED * rand() / (RAND_MAX)));
  } else {
    live_monsters[live_monster_count].speed_x = m->start_speed_x;
    live_monsters[live_monster_count].speed_y = m->start_speed_y;
  }
  live_monsters[live_monster_count].dir = DIR_NONE;
  live_monsters[live_monster_count].face = 0;
  for(i = 0; i < m->image_count; i++) {
    if(m->image_index[i] == image_index) {
      live_monsters[live_monster_count].face = i;
    }
  }
  live_monsters[live_monster_count].remove_me = 0;
  live_monsters[live_monster_count].monster = m;
  live_monsters[live_monster_count].child_count = 0;
  // allocate custom storage
  if(live_monsters[live_monster_count].monster->allocCustom) {
    live_monsters[live_monster_count].monster->
      allocCustom(&live_monsters[live_monster_count]);
  }
  live_monster_count++;

  // remove image from map
  if(change_map) {
    map.image_index[LEVEL_MAIN][x + (y * map.w)] = EMPTY_MAP;
  }
}

void
removeLiveMonster(int live_monster_index)
{
  int i, t;
  LiveMonster *p;

  // debug
  if(live_monster_index >= live_monster_count) {
    fprintf(stderr,
            "Trying to remove monster w. index out of bounds: count=%d index=%d\n",
            live_monster_count, live_monster_index);
    for(t = 0; t < live_monster_count; t++) {
      fprintf(stderr, "\tmonster=%s x=%d y=%d\n",
              live_monsters[t].monster->name, live_monsters[t].pos_x,
              live_monsters[t].pos_y);
    }
    fflush(stderr);
    exit(-1);
  }
  // add it back to the map
  p = &live_monsters[live_monster_index];
  // If the monster was bred (bullets) it doesn't need to be saved in the map.
  if(!p->parent) {
    setImageNoCheck(LEVEL_MAIN,
                    p->pos_x, p->pos_y,
                    p->monster->image_index[getLiveMonsterFace(p)]);
    // remove its children
    for(t = 0; p->child_count && t < live_monster_count; t++) {
      if(live_monsters[t].parent == p) {
        removeLiveMonster(t);
        t--;
      }
    }
  } else {
    p->parent->child_count--;
  }

  // free custom storage
  if(live_monsters[live_monster_index].monster->allocCustom) {
    free(live_monsters[live_monster_index].custom);
  }
  // remove it from memory
  for(t = live_monster_index; t < live_monster_count - 1; t++) {
    for(i = 0; i < live_monster_count; i++) {
      if(live_monsters[i].parent == &live_monsters[t + 1]) {
        live_monsters[i].parent = &live_monsters[t];
      }
    }
    memcpy(&live_monsters[t], &live_monsters[t + 1], sizeof(LiveMonster));
  }
  live_monster_count--;
}

void
debugMonsters()
{
  int t, i, n;
  fprintf(stderr, "Monsters:\n");
  for(t = 0; t < live_monster_count; t++) {
    n = -1;
    for(i = 0; live_monsters[t].parent && i < live_monster_count; i++) {
      if(&live_monsters[i] == live_monsters[t].parent) {
        n = i;
        break;
      }
    }
    fprintf(stderr,
            "\t%d monster=%s x=%d y=%d child_count=%d remove_me=%d parent=%d\n",
            t, live_monsters[t].monster->name, live_monsters[t].pos_x,
            live_monsters[t].pos_y, live_monsters[t].child_count,
            live_monsters[t].remove_me, n);
  }
  fflush(stderr);
}

void
removeAllLiveMonsters()
{
  while(live_monster_count > 0) {
    removeLiveMonster(0);
  }
}

/**
   Here rect is in pixels where 0, 0 is the screen's left top corner.
   Returns 0 for false and non-0 for true.
 */
int
isOnScreen(SDL_Rect * rect)
{
  return intersects(rect, &extended_screen_rect);
}

/**
   Draw all currently tracked creatures.
   start_x, start_y are the offset of the screen's top left edge in pixels.
 */
void
drawLiveMonsters(SDL_Surface * surface, int start_x, int start_y)
{
  SDL_Rect pos;
  SDL_Surface *img;
  int i;

  for(i = 0; i < live_monster_count; i++) {
    if(move_monsters)
      live_monsters[i].monster->moveMonster(&live_monsters[i]);

    img =
      images[live_monsters[i].monster->
             image_index[getLiveMonsterFace(&live_monsters[i])]]->image;
    pos.x =
      live_monsters[i].pos_x * TILE_W - start_x + live_monsters[i].pixel_x;
    pos.y =
      live_monsters[i].pos_y * TILE_H - start_y + live_monsters[i].pixel_y;
    pos.w = img->w;
    pos.h = img->h;

    if(live_monsters[i].remove_me || !isOnScreen(&pos)) {
      removeLiveMonster(i);
    } else {
      live_monsters[i].monster->drawMonster(&pos, &live_monsters[i], surface,
                                            img);

      if(live_monsters[i].monster->breeds != NULL &&
         live_monsters[i].monster->max_children >
         live_monsters[i].child_count) {
        live_monsters[i].monster->breedMonster(&live_monsters[i], &pos);
      }
    }
  }
}

int
getLiveMonsterFace(LiveMonster * live)
{
  return live->face / live->monster->face_mod;
}

/**
   Return live monster if there's a one at position pos,
   NULL otherwise.
 */
LiveMonster *
detectMonster(Position * pos)
{
  int i;
  for(i = 0; i < live_monster_count; i++) {
    if(live_monsters[i].monster->detectMonster(pos, &live_monsters[i])) {
      return &live_monsters[i];
    }
  }
  return NULL;
}
