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|ENT_SWIMS)))
273 if (!map.isSolid(x, y))
275 enemy->tx = (int)enemy->x;
280 if ((int)enemy->x == enemy->tx)
282 if ((Math::prand() % 100) == 0)
284 enemy->tx = (int)(enemy->x + Math::rrand(-640, 640));
285 enemy->ty = (int)(enemy->y);
286 if ((enemy->flags & ENT_FLIES) || (enemy->flags & ENT_SWIMS))
288 enemy->ty = (int)(enemy->y + Math::rrand(-320, 320));
292 Math::limitInt(&enemy->tx, 15, (MAPWIDTH * BRICKSIZE)- 20);
293 Math::limitInt(&enemy->ty, 15, (MAPHEIGHT * BRICKSIZE)- 20);
295 if (map.isSolid((enemy->tx >> BRICKSHIFT), (enemy->ty >> BRICKSHIFT)))
297 enemy->tx = (int)enemy->x;
298 enemy->ty = (int)enemy->y;
302 // Don't enter areas you're not supposed to
303 if (enemy->ty != (int)enemy->y)
305 if (enemy->flags & ENT_FLIES)
307 if (map.isLiquid(x, y + 1))
309 enemy->ty = (int)enemy->y;
314 if ((int)enemy->y == enemy->ty)
316 enemy->y = enemy->ty;
323 if ((enemy->flags & ENT_FLIES) || (enemy->flags & ENT_SWIMS))
325 enemy->dx = enemy->dy = 0;
327 if ((int)enemy->y < enemy->ty) enemy->dy = 1;
328 if ((int)enemy->y > enemy->ty) enemy->dy = -1;
331 if ((int)enemy->x == enemy->tx) {enemy->dx = 0;}
332 if ((int)enemy->x < enemy->tx) {enemy->dx = 1; enemy->face = 0;}
333 if ((int)enemy->x > enemy->tx) {enemy->dx = -1; enemy->face = 1;}
335 if ((enemy->flags & ENT_SWIMS) && (enemy->environment == ENV_WATER))
339 if ((int)enemy->y < enemy->ty) enemy->dy = 1;
340 if ((int)enemy->y > enemy->ty) enemy->dy = -1;
343 lookForPlayer(enemy);
348 int old = game.currentComboHits;
352 if (old == 24 && game.currentComboHits == 25)
354 presentPlayerMedal("25_Hit_Combo");
358 void enemyBulletCollisions(Entity *bullet)
360 if (bullet->health < 1)
365 Entity *enemy = (Entity*)map.enemyList.getHead();
367 while (enemy->next != NULL)
369 enemy = (Entity*)enemy->next;
371 if ((enemy->flags & ENT_TELEPORTING) || (enemy->dead == DEAD_DYING))
376 char comboString[100];
378 if ((bullet->owner == &player) || (bullet->owner == &engine.world) || (bullet->flags & ENT_BOSS))
380 sprintf(comboString, "Combo-%s", bullet->name);
382 if (Collision::collision(enemy, bullet))
384 if (bullet->id != WP_LASER)
389 Math::removeBit(&bullet->flags, ENT_SPARKS);
390 Math::removeBit(&bullet->flags, ENT_PUFFS);
392 if ((enemy->flags & ENT_IMMUNE) && (!(enemy->flags & ENT_STATIC)))
394 bullet->health = 0; // include the Laser for this one!
395 enemy->owner->tx = (int)bullet->owner->x;
396 enemy->owner->ty = (int)bullet->owner->y;
397 if (enemy->x < enemy->tx) {enemy->owner->dx = 1; enemy->owner->face = 0;}
398 if (enemy->x > enemy->tx) {enemy->owner->dx = -1; enemy->owner->face = 1;}
403 Increment the bullet hits counter. The laser can only do this
404 if the target has more than 0 health. Overwise the stats screen
405 can show an accurracy of 800%. Which is just plain silly.
407 if (bullet->owner == &player)
409 enemy->tx = (int)player.x;
410 enemy->ty = (int)player.y;
413 if (player.x < enemy->x)
418 if ((bullet->id != WP_LASER) || (enemy->health > 0))
420 game.incBulletsHit();
424 if (!(enemy->flags & ENT_EXPLODES))
426 audio.playSound(SND_HIT, CH_ANY);
429 addBlood(enemy, bullet->dx / 4, Math::rrand(-6, -3), 1);
433 addColorParticles(bullet->x, bullet->y, Math::rrand(25, 75), -1);
438 audio.playSound(SND_CLANG, CH_ANY);
439 addColorParticles(bullet->x, bullet->y, Math::rrand(25, 75), -1);
442 if (enemy->health > 0)
444 if (!(enemy->flags & ENT_IMMUNE))
446 enemy->health -= bullet->damage;
449 if (enemy->health <= 0)
451 if (bullet->owner == &player)
453 addPlayerScore(enemy->value);
454 game.currentMissionEnemiesDefeated++;
456 if (player.currentWeapon != &weapon[WP_LASER])
461 checkObjectives(comboString, false);
462 checkObjectives("Enemy", false);
463 checkObjectives(enemy->name, false);
466 if (!(enemy->flags & ENT_STATIC))
468 enemy->dx = (bullet->dx / 4);
471 if (enemy->flags & ENT_EXPLODES)
473 audio.playSound(SND_ELECDEATH1 + Math::prand() % 3, CH_DEATH);
477 audio.playSound(SND_DEATH1 + Math::prand() % 3, CH_DEATH);
484 if (enemy->flags & ENT_STATIC)
489 if (enemy->health < 0)
491 enemy->dx = Math::rrand(-3, 3);
492 enemy->dy = 5 - Math::prand() % 15;
495 if (enemy->flags & ENT_EXPLODES)
497 audio.playSound(SND_ELECDEATH1 + Math::prand() % 3, CH_DEATH);
501 audio.playSound(SND_DEATH1 + Math::prand() % 3, CH_DEATH);
504 if (bullet->owner == &player)
506 if (player.currentWeapon != &weapon[WP_LASER])
509 checkObjectives(comboString, false);
514 if (game.currentComboHits >= 3)
517 sprintf(message, _("%d Hit Combo!"), game.currentComboHits);
518 engine.setInfoMessage(message, 0, INFO_NORMAL);
527 int getNonGoreParticleColor(const char *name)
529 int rtn = graphics.yellow;
531 if (strcmp(name, "Pistol Blob") == 0)
533 rtn = graphics.green;
535 else if (strcmp(name, "Grenade Blob") == 0)
537 rtn = graphics.skyBlue;
539 else if (strcmp(name, "Aqua Blob") == 0)
543 else if (strcmp(name, "Laser Blob") == 0)
545 rtn = SDL_MapRGB(graphics.screen->format, 255, 0, 255);
547 else if (strcmp(name, "Machine Gun Blob") == 0)
549 rtn = SDL_MapRGB(graphics.screen->format, 200, 64, 24);
555 void gibEnemy(Entity *enemy)
557 if (enemy->flags & ENT_GALDOV)
559 addTeleportParticles(enemy->x, enemy->y, 75, SND_TELEPORT3);
560 checkObjectives("Galdov", true);
563 if (enemy->flags & ENT_EXPLODES)
565 addExplosion(enemy->x + (enemy->width / 2), enemy->y + (enemy->height / 2), 10 + (20 * game.skill), enemy);
566 addSmokeAndFire(enemy, Math::rrand(-5, 5), Math::rrand(-5, 5), 2);
571 int amount = (game.gore) ? 25 : 150;
572 int color = getNonGoreParticleColor(enemy->name);
574 for (int i = 0 ; i < amount ; i++)
576 x = enemy->x + Math::rrand(-3, 3);
577 y = enemy->y + Math::rrand(-3, 3);
581 dx = Math::rrand(-5, 5);
582 dy = Math::rrand(-15, -5);
583 addEffect(x, y, dx, dy, EFF_BLEEDS);
587 dx = Math::rrand(-5, 5);
588 dy = Math::rrand(-5, 5);
589 addColoredEffect(x, y, dx, dy, color, EFF_COLORED + EFF_WEIGHTLESS);
593 (game.gore) ? audio.playSound(SND_SPLAT, CH_ANY) : audio.playSound(SND_POP, CH_ANY);
598 Entity *enemy = (Entity*)map.enemyList.getHead();
599 Entity *previous = enemy;
601 map.fightingGaldov = false;
603 int x, y, absX, absY;
605 while (enemy->next != NULL)
607 enemy = (Entity*)enemy->next;
609 if (!engine.cheatBlood)
611 if (enemy->dead == DEAD_DYING)
613 if (!enemy->referenced)
615 debug(("Removing unreferenced enemy '%s'\n", enemy->name));
616 map.enemyList.remove(previous, enemy);
624 enemy->referenced = false;
629 x = (int)(enemy->x - engine.playerPosX);
630 y = (int)(enemy->y - engine.playerPosY);
635 if ((absX < 800) && (absY < 600))
638 if (enemy->flags & ENT_FLIES)
643 if (enemy->owner->flags & ENT_TELEPORTING)
649 if ((enemy->health > 0) && (!(enemy->flags & ENT_STATIC)))
653 if (enemy->owner == enemy)
659 lookForPlayer(enemy);
663 if (map.isBlizzardLevel)
665 enemy->dx += map.windPower * 0.1;
668 if (enemy->flags & ENT_NOMOVE)
675 if ((absX < 700) && (absY < 500))
677 if (enemy->flags & ENT_FIRETRAIL)
679 addFireTrailParticle(enemy->x + (enemy->face * 16) + Math::rrand(-1, 1), enemy->y + Math::rrand(-1, 1));
682 graphics.blit(enemy->getFaceImage(), x, y, graphics.screen, false);
684 if ((enemy->dx != 0) || (enemy->flags & ENT_FLIES) || (enemy->flags & ENT_STATIC))
693 if (enemy->flags & ENT_SPAWNED)
695 if ((absX > 1920) || (absY > 1440))
697 enemy->health = -100;
702 if (enemy->health > 0)
706 if ((enemy->environment == ENV_SLIME) || (enemy->environment == ENV_LAVA))
708 checkObjectives(enemy->name, false);
714 if (enemy->flags & ENT_GALDOV)
719 if (enemy->health == 0)
721 Math::removeBit(&enemy->flags, ENT_WEIGHTLESS);
722 Math::removeBit(&enemy->flags, ENT_SWIMS);
723 Math::removeBit(&enemy->flags, ENT_FLIES);
724 Math::addBit(&enemy->flags, ENT_INANIMATE);
725 Math::addBit(&enemy->flags, ENT_BOUNCES);
726 enemy->health = -1 - Math::prand() % 25;
729 if (engine.cheatBlood)
731 if (!(enemy->flags & ENT_EXPLODES))
733 if ((enemy->health % 4) == 0)
735 addBlood(enemy, Math::rrand(-2, 2), Math::rrand(-6, -3), 1);
737 else if ((enemy->health % 10) == 0)
741 audio.playSound(SND_DEATH1 + Math::prand() % 3, CH_DEATH);
749 if (enemy->flags & ENT_MULTIEXPLODE)
751 if (enemy->health < -30)
753 if ((enemy->health % 3) == 0)
755 addExplosion(enemy->x + Math::prand() % 25, enemy->y + Math::prand() % 25, 10 + (20 * game.skill), enemy);
756 addSmokeAndFire(enemy, Math::rrand(-5, 5), Math::rrand(-5, 5), 2);
761 if (enemy->health > -50)
767 if (enemy->flags & ENT_GALDOVFINAL)
770 enemy->dx = Math::rrand(-10, 10);
771 enemy->dy = Math::rrand(-10, 10);
775 if (enemy->dead == DEAD_ALIVE)
777 if ((absX < 800) && (absY < 600))
783 dropRandomItems((int)enemy->x, (int)enemy->y);
787 enemy->dead = DEAD_DYING;
790 if (enemy->dead == DEAD_DYING)
792 if (!enemy->referenced)
794 if ((absX < 800) && (absY < 600))
800 dropRandomItems((int)enemy->x, (int)enemy->y);
804 debug(("Removing unreferenced enemy '%s'\n", enemy->name));
805 map.enemyList.remove(previous, enemy);
813 // default the enemy to not referenced.
814 // doBullets() will change this if required.
815 enemy->referenced = false;
819 void loadEnemy(const char *token)
823 for (int i = MAX_ENEMIES ; i > -1 ; i--)
824 if (strcmp(defEnemy[i].name, "") == 0)
829 printf("Out of enemy define space!\n");
833 char name[50], sprite[3][100], weapon[100], flags[1024];
836 sscanf(token, "%*c %[^\"] %*c %s %s %s %*c %[^\"] %*c %d %d %s", name, sprite[0], sprite[1], sprite[2], weapon, &health, &value, flags);
838 defEnemy[enemy].setName(name);
839 defEnemy[enemy].setSprites(graphics.getSprite(sprite[0], true), graphics.getSprite(sprite[1], true), graphics.getSprite(sprite[2], true));
840 defEnemy[enemy].currentWeapon = getWeaponByName(weapon);
841 defEnemy[enemy].health = health;
842 defEnemy[enemy].value = value;
844 defEnemy[enemy].flags = engine.getValueOfFlagTokens(flags);
847 void loadDefEnemies()
849 for (int i = 0 ; i < MAX_ENEMIES ; i++)
851 strcpy(defEnemy[i].name, "");
856 if (!engine.loadData("data/defEnemies"))
858 graphics.showErrorAndExit("Couldn't load enemy definitions file (%s)", "data/defEnemies");
861 char *token = strtok((char*)engine.dataBuffer, "\n");
863 char name[50], sprite[3][100], weapon[100], flags[1024];
868 if (strcmp(token, "@EOF@") == 0)
873 sscanf(token, "%*c %[^\"] %*c %s %s %s %*c %[^\"] %*c %d %d %s", name, sprite[0], sprite[1], sprite[2], weapon, &health, &value, flags);
875 defEnemy[enemy].setName(name);
876 defEnemy[enemy].setSprites(graphics.getSprite(sprite[0], true), graphics.getSprite(sprite[1], true), graphics.getSprite(sprite[2], true));
877 defEnemy[enemy].currentWeapon = getWeaponByName(weapon);
878 defEnemy[enemy].health = health;
879 defEnemy[enemy].value = value;
880 defEnemy[enemy].flags = engine.getValueOfFlagTokens(flags);
884 token = strtok(NULL, "\n");