2 Copyright (C) 2004 Parallel Realities
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 See the GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 Entity *getDefinedEnemy(const char *name)
25 for (int i = 0 ; i < MAX_ENEMIES ; i++)
27 if (strcmp(name, defEnemy[i].name) == 0)
33 debug(("No Such Enemy '%s'\n", name));
38 Entity *getEnemy(const char *name)
40 Entity *enemy = (Entity*)map.enemyList.getHead();
42 while (enemy->next != NULL)
44 enemy = (Entity*)enemy->next;
46 if (strcmp(name, enemy->name) == 0)
52 debug(("No Such Enemy '%s'\n", name));
57 void addEnemy(const char *name, int x, int y, int flags)
59 Entity *enemy = new Entity();
60 Entity *defEnemy = getDefinedEnemy(name);
64 debug(("ERROR : COULDN'T FIND ENEMY '%s'!\n", name));
68 enemy->setName(defEnemy->name);
69 enemy->setSprites(defEnemy->sprite[0], defEnemy->sprite[1], defEnemy->sprite[2]);
70 enemy->currentWeapon = defEnemy->currentWeapon;
71 enemy->value = defEnemy->value;
72 enemy->health = defEnemy->health;
73 enemy->flags = defEnemy->flags;
76 enemy->setVelocity(0, 0);
77 enemy->baseThink = 60;
79 enemy->flags += flags;
81 enemy->reload = 120; // Wait about seconds seconds before attacking
83 if (map.data[(int)(enemy->x) >> BRICKSHIFT][(int)(enemy->y) >> BRICKSHIFT] == MAP_WATER)
85 enemy->environment = ENV_WATER;
91 bool hasClearShot(Entity *enemy)
98 Math::calculateSlope(player.x, player.y, enemy->x, enemy->y, &dx, &dy);
100 if ((dx == 0) && (dy == 0))
108 //graphics.blit(graphics.getSprite("AimedShot", true)->getCurrentFrame(), (int)(x - engine.playerPosX), (int)(y - engine.playerPosY), graphics.screen, true);
110 mx = (int)(x) >> BRICKSHIFT;
111 my = (int)(y) >> BRICKSHIFT;
113 if ((mx < 0) || (my < 0))
116 if (map.isSolid(mx, my))
119 if (Collision::collision(x, y, 3, 3, (int)player.x, (int)player.y, player.height, player.width))
126 void lookForPlayer(Entity *enemy)
129 if (player.health <= -60)
132 if (game.missionOverReason == MIS_COMPLETE)
135 // can't fire anyway!
136 if (enemy->reload > 0)
139 int x = (int)fabs(enemy->x - player.x);
140 int y = (int)fabs(enemy->y - player.y);
146 // can't even jump that high!
150 // Player is in range... go for them!
151 if (enemy->flags & ENT_ALWAYSCHASE)
153 enemy->owner->tx = (int)(player.x);
154 enemy->owner->ty = (int)(player.y);
156 else if ((Math::prand() % (35 - game.skill)) == 0)
158 enemy->owner->tx = (int)(player.x);
159 enemy->owner->ty = (int)(player.y);
162 // facing the wrong way
163 if ((enemy->face == 0) && (player.x < enemy->x))
168 // still facing the wrong way
169 if ((enemy->face == 1) && (player.x > enemy->x))
174 if (hasClearShot(enemy))
176 if (enemy->flags & ENT_ALWAYSFIRES)
178 addBullet(enemy, enemy->currentWeapon->getSpeed(enemy->face), 0);
179 if (enemy->currentWeapon == &weapon[WP_ALIENSPREAD])
181 addBullet(enemy, enemy->currentWeapon->getSpeed(enemy->face), 2);
182 addBullet(enemy, enemy->currentWeapon->getSpeed(enemy->face), -2);
185 else if ((Math::prand() % 850) <= (game.skill * 5))
187 addBullet(enemy, enemy->currentWeapon->getSpeed(enemy->face), 0);
188 if (enemy->currentWeapon == &weapon[WP_ALIENSPREAD])
190 addBullet(enemy, enemy->currentWeapon->getSpeed(enemy->face), 2);
191 addBullet(enemy, enemy->currentWeapon->getSpeed(enemy->face), -2);
195 if (enemy->flags & ENT_RAPIDFIRE)
197 if (enemy->flags & ENT_ALWAYSFIRES)
199 if ((Math::prand() % 25) > game.skill * 3)
200 Math::removeBit(&enemy->flags, ENT_ALWAYSFIRES);
204 if ((Math::prand() % 50) < game.skill * 2)
205 Math::addBit(&enemy->flags, ENT_ALWAYSFIRES);
211 if (enemy->flags & ENT_RAPIDFIRE)
212 Math::removeBit(&enemy->flags, ENT_ALWAYSFIRES);
215 if ((enemy->flags & ENT_FLIES) || (enemy->flags & ENT_SWIMS) || (enemy->flags & ENT_NOJUMP))
218 if (enemy->flags & ENT_JUMPS)
222 if ((Math::prand() % 25) == 0)
224 int distance = Math::rrand(1, 4);
225 enemy->setVelocity(distance - ((distance * 2) * enemy->face), Math::rrand(-12, -10));
232 // Jump to try and reach player (even if they are approximately level with you!)
233 if (player.y - 5 < enemy->y)
237 if ((Math::prand() % 100) == 0)
245 void doAI(Entity *enemy)
247 if (enemy->flags & ENT_GALDOV)
251 else if (enemy->flags & ENT_BOSS)
256 int x = (int)enemy->x;
257 int y = (int)enemy->y + enemy->height;
266 enemy->tx = (int)enemy->x;
268 // Don't enter areas you're not supposed to
269 if (enemy->tx != (int)enemy->x)
271 if (!(enemy->flags & ENT_FLIES))
273 //if ((map.data[x][y] == MAP_AIR) || (map.data[x][y] >= MAP_DECORATION))
274 if (!map.isSolid(x, y))
276 enemy->tx = (int)enemy->x;
281 if ((int)enemy->x == enemy->tx)
283 if ((Math::prand() % 100) == 0)
285 enemy->tx = (int)(enemy->x + Math::rrand(-640, 640));
286 enemy->ty = (int)(enemy->y);
287 if ((enemy->flags & ENT_FLIES) || (enemy->flags & ENT_SWIMS))
289 enemy->ty = (int)(enemy->y + Math::rrand(-320, 320));
293 Math::limitInt(&enemy->tx, 15, (MAPWIDTH * BRICKSIZE)- 20);
294 Math::limitInt(&enemy->ty, 15, (MAPHEIGHT * BRICKSIZE)- 20);
296 if (map.isSolid((enemy->tx >> BRICKSHIFT), (enemy->ty >> BRICKSHIFT)))
298 enemy->tx = (int)enemy->x;
299 enemy->ty = (int)enemy->y;
303 // Don't enter areas you're not supposed to
304 if (enemy->ty != (int)enemy->y)
306 if (enemy->flags & ENT_FLIES)
308 if (map.isLiquid(x, y + 1))
310 enemy->ty = (int)enemy->y;
315 if ((int)enemy->y == enemy->ty)
317 enemy->y = enemy->ty;
324 if ((enemy->flags & ENT_FLIES) || (enemy->flags & ENT_SWIMS))
326 enemy->dx = enemy->dy = 0;
328 if ((int)enemy->y < enemy->ty) enemy->dy = 1;
329 if ((int)enemy->y > enemy->ty) enemy->dy = -1;
332 if ((int)enemy->x == enemy->tx) {enemy->dx = 0;}
333 if ((int)enemy->x < enemy->tx) {enemy->dx = 1; enemy->face = 0;}
334 if ((int)enemy->x > enemy->tx) {enemy->dx = -1; enemy->face = 1;}
336 if ((enemy->flags & ENT_SWIMS) && (enemy->environment == ENV_WATER))
340 if ((int)enemy->y < enemy->ty) enemy->dy = 1;
341 if ((int)enemy->y > enemy->ty) enemy->dy = -1;
344 lookForPlayer(enemy);
347 void enemyBulletCollisions(Entity *bullet)
349 if (bullet->health < 1)
354 Entity *enemy = (Entity*)map.enemyList.getHead();
356 while (enemy->next != NULL)
358 enemy = (Entity*)enemy->next;
360 if ((enemy->flags & ENT_TELEPORTING) || (enemy->dead == DEAD_DYING))
365 char comboString[100];
367 if ((bullet->owner == &player) || (bullet->owner == &engine.world) || (bullet->flags & ENT_BOSS))
369 sprintf(comboString, "Combo-%s", bullet->name);
371 if (Collision::collision(enemy, bullet))
373 if (bullet->id != WP_LASER)
378 Math::removeBit(&bullet->flags, ENT_SPARKS);
379 Math::removeBit(&bullet->flags, ENT_PUFFS);
381 if ((enemy->flags & ENT_IMMUNE) && (!(enemy->flags & ENT_STATIC)))
383 bullet->health = 0; // include the Laser for this one!
384 enemy->owner->tx = (int)bullet->owner->x;
385 enemy->owner->ty = (int)bullet->owner->y;
386 if (enemy->x < enemy->tx) {enemy->owner->dx = 1; enemy->owner->face = 0;}
387 if (enemy->x > enemy->tx) {enemy->owner->dx = -1; enemy->owner->face = 1;}
392 Increment the bullet hits counter. The laser can only do this
393 if the target has more than 0 health. Overwise the stats screen
394 can show an accurracy of 800%. Which is just plain silly.
396 if (bullet->owner == &player)
398 enemy->tx = (int)player.x;
399 enemy->ty = (int)player.y;
402 if (player.x < enemy->x)
407 if ((bullet->id != WP_LASER) || (enemy->health > 0))
409 game.incBulletsHit();
413 if (!(enemy->flags & ENT_EXPLODES))
415 audio.playSound(SND_HIT, CH_ANY);
418 addBlood(enemy, bullet->dx / 4, Math::rrand(-6, -3), 1);
422 addColorParticles(bullet->x, bullet->y, Math::rrand(25, 75), -1);
427 audio.playSound(SND_CLANG, CH_ANY);
428 addColorParticles(bullet->x, bullet->y, Math::rrand(25, 75), -1);
431 if (enemy->health > 0)
433 if (!(enemy->flags & ENT_IMMUNE))
435 enemy->health -= bullet->damage;
438 if (enemy->health <= 0)
440 if (bullet->owner == &player)
442 game.score += enemy->value;
443 game.currentMissionEnemiesDefeated++;
445 if (player.currentWeapon != &weapon[WP_LASER])
450 checkObjectives(comboString, false);
451 checkObjectives("Enemy", false);
452 checkObjectives(enemy->name, false);
455 if (!(enemy->flags & ENT_STATIC))
457 enemy->dx = (bullet->dx / 4);
460 if (enemy->flags & ENT_EXPLODES)
462 audio.playSound(SND_ELECDEATH1 + Math::prand() % 3, CH_DEATH);
466 audio.playSound(SND_DEATH1 + Math::prand() % 3, CH_DEATH);
473 if (enemy->flags & ENT_STATIC)
478 if (enemy->health < 0)
480 enemy->dx = Math::rrand(-3, 3);
481 enemy->dy = 5 - Math::prand() % 15;
484 if (enemy->flags & ENT_EXPLODES)
486 audio.playSound(SND_ELECDEATH1 + Math::prand() % 3, CH_DEATH);
490 audio.playSound(SND_DEATH1 + Math::prand() % 3, CH_DEATH);
493 if (bullet->owner == &player)
495 if (player.currentWeapon != &weapon[WP_LASER])
498 checkObjectives(comboString, false);
503 if (game.currentComboHits >= 3)
506 sprintf(message, _("%d Hit Combo!"), game.currentComboHits);
507 engine.setInfoMessage(message, 0, INFO_NORMAL);
516 int getNonGoreParticleColor(const char *name)
518 int rtn = graphics.yellow;
520 if (strcmp(name, "Pistol Blob") == 0)
522 rtn = graphics.green;
524 else if (strcmp(name, "Grenade Blob") == 0)
526 rtn = graphics.skyBlue;
528 else if (strcmp(name, "Aqua Blob") == 0)
532 else if (strcmp(name, "Laser Blob") == 0)
534 rtn = SDL_MapRGB(graphics.screen->format, 255, 0, 255);
536 else if (strcmp(name, "Machine Gun Blob") == 0)
538 rtn = SDL_MapRGB(graphics.screen->format, 200, 64, 24);
544 void gibEnemy(Entity *enemy)
546 if (enemy->flags & ENT_GALDOV)
548 addTeleportParticles(enemy->x, enemy->y, 75, SND_TELEPORT3);
549 checkObjectives("Galdov", true);
552 if (enemy->flags & ENT_EXPLODES)
554 addExplosion(enemy->x + (enemy->width / 2), enemy->y + (enemy->height / 2), 10 + (20 * game.skill), enemy);
555 addSmokeAndFire(enemy, Math::rrand(-5, 5), Math::rrand(-5, 5), 2);
560 int amount = (game.gore) ? 25 : 150;
561 int color = getNonGoreParticleColor(enemy->name);
563 for (int i = 0 ; i < amount ; i++)
565 x = enemy->x + Math::rrand(-3, 3);
566 y = enemy->y + Math::rrand(-3, 3);
570 dx = Math::rrand(-5, 5);
571 dy = Math::rrand(-15, -5);
572 addEffect(x, y, dx, dy, EFF_BLEEDS);
576 dx = Math::rrand(-5, 5);
577 dy = Math::rrand(-5, 5);
578 addColoredEffect(x, y, dx, dy, color, EFF_COLORED + EFF_WEIGHTLESS);
582 (game.gore) ? audio.playSound(SND_SPLAT, CH_ANY) : audio.playSound(SND_POP, CH_ANY);
587 Entity *enemy = (Entity*)map.enemyList.getHead();
588 Entity *previous = enemy;
590 map.fightingGaldov = false;
592 int x, y, absX, absY;
594 while (enemy->next != NULL)
596 enemy = (Entity*)enemy->next;
598 if (!engine.cheatBlood)
600 if (enemy->dead == DEAD_DYING)
602 if (!enemy->referenced)
604 debug(("Removing unreferenced enemy '%s'\n", enemy->name));
605 map.enemyList.remove(previous, enemy);
613 enemy->referenced = false;
618 x = (int)(enemy->x - engine.playerPosX);
619 y = (int)(enemy->y - engine.playerPosY);
624 if ((absX < 800) && (absY < 600))
627 if (enemy->flags & ENT_FLIES)
632 if (enemy->owner->flags & ENT_TELEPORTING)
638 if ((enemy->health > 0) && (!(enemy->flags & ENT_STATIC)))
642 if (enemy->owner == enemy)
648 lookForPlayer(enemy);
652 if (map.isBlizzardLevel)
654 enemy->dx += map.windPower * 0.1;
657 if (enemy->flags & ENT_NOMOVE)
664 if ((absX < 700) && (absY < 500))
666 if (enemy->flags & ENT_FIRETRAIL)
668 addFireTrailParticle(enemy->x + (enemy->face * 16) + Math::rrand(-1, 1), enemy->y + Math::rrand(-1, 1));
671 graphics.blit(enemy->getFaceImage(), x, y, graphics.screen, false);
673 if ((enemy->dx != 0) || (enemy->flags & ENT_FLIES) || (enemy->flags & ENT_STATIC))
682 if (enemy->flags & ENT_SPAWNED)
684 if ((absX > 1920) || (absY > 1440))
686 enemy->health = -100;
691 if (enemy->health > 0)
695 if ((enemy->environment == ENV_SLIME) || (enemy->environment == ENV_LAVA))
697 checkObjectives(enemy->name, false);
703 if (enemy->flags & ENT_GALDOV)
708 if (enemy->health == 0)
710 Math::removeBit(&enemy->flags, ENT_WEIGHTLESS);
711 Math::removeBit(&enemy->flags, ENT_SWIMS);
712 Math::removeBit(&enemy->flags, ENT_FLIES);
713 Math::addBit(&enemy->flags, ENT_INANIMATE);
714 Math::addBit(&enemy->flags, ENT_BOUNCES);
715 enemy->health = -1 - Math::prand() % 25;
718 if (engine.cheatBlood)
720 if (!(enemy->flags & ENT_EXPLODES))
722 if ((enemy->health % 4) == 0)
724 addBlood(enemy, Math::rrand(-2, 2), Math::rrand(-6, -3), 1);
726 else if ((enemy->health % 10) == 0)
730 audio.playSound(SND_DEATH1 + Math::prand() % 3, CH_DEATH);
738 if (enemy->flags & ENT_MULTIEXPLODE)
740 if (enemy->health < -30)
742 if ((enemy->health % 3) == 0)
744 addExplosion(enemy->x + Math::prand() % 25, enemy->y + Math::prand() % 25, 10 + (20 * game.skill), enemy);
745 addSmokeAndFire(enemy, Math::rrand(-5, 5), Math::rrand(-5, 5), 2);
750 if (enemy->health > -50)
756 if (enemy->flags & ENT_GALDOVFINAL)
759 enemy->dx = Math::rrand(-10, 10);
760 enemy->dy = Math::rrand(-10, 10);
764 if (enemy->dead == DEAD_ALIVE)
766 if ((absX < 800) && (absY < 600))
772 dropRandomItems((int)enemy->x, (int)enemy->y);
776 enemy->dead = DEAD_DYING;
779 if (enemy->dead == DEAD_DYING)
781 if (!enemy->referenced)
783 if ((absX < 800) && (absY < 600))
789 dropRandomItems((int)enemy->x, (int)enemy->y);
793 debug(("Removing unreferenced enemy '%s'\n", enemy->name));
794 map.enemyList.remove(previous, enemy);
802 // default the enemy to not referenced.
803 // doBullets() will change this if required.
804 enemy->referenced = false;
808 void loadEnemy(const char *token)
812 for (int i = MAX_ENEMIES ; i > -1 ; i--)
813 if (strcmp(defEnemy[i].name, "") == 0)
818 printf("Out of enemy define space!\n");
822 char name[50], sprite[3][100], weapon[100], flags[1024];
825 sscanf(token, "%*c %[^\"] %*c %s %s %s %*c %[^\"] %*c %d %d %s", name, sprite[0], sprite[1], sprite[2], weapon, &health, &value, flags);
827 defEnemy[enemy].setName(name);
828 defEnemy[enemy].setSprites(graphics.getSprite(sprite[0], true), graphics.getSprite(sprite[1], true), graphics.getSprite(sprite[2], true));
829 defEnemy[enemy].currentWeapon = getWeaponByName(weapon);
830 defEnemy[enemy].health = health;
831 defEnemy[enemy].value = value;
833 defEnemy[enemy].flags = engine.getValueOfFlagTokens(flags);
836 void loadDefEnemies()
838 for (int i = 0 ; i < MAX_ENEMIES ; i++)
840 strcpy(defEnemy[i].name, "");
845 if (!engine.loadData("data/defEnemies"))
847 graphics.showErrorAndExit("Couldn't load enemy definitions file (%s)", "data/defEnemies");
850 char *token = strtok((char*)engine.dataBuffer, "\n");
852 char name[50], sprite[3][100], weapon[100], flags[1024];
857 if (strcmp(token, "@EOF@") == 0)
862 sscanf(token, "%*c %[^\"] %*c %s %s %s %*c %[^\"] %*c %d %d %s", name, sprite[0], sprite[1], sprite[2], weapon, &health, &value, flags);
864 defEnemy[enemy].setName(name);
865 defEnemy[enemy].setSprites(graphics.getSprite(sprite[0], true), graphics.getSprite(sprite[1], true), graphics.getSprite(sprite[2], true));
866 defEnemy[enemy].currentWeapon = getWeaponByName(weapon);
867 defEnemy[enemy].health = health;
868 defEnemy[enemy].value = value;
869 defEnemy[enemy].flags = engine.getValueOfFlagTokens(flags);
873 token = strtok(NULL, "\n");