2 Copyright (C) 2004-2011 Parallel Realities
3 Copyright (C) 2011-2015 Perpendicular Dimensions
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14 See the GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24 Entity *getDefinedEnemy(const char *name)
26 for (int i = 0 ; i < MAX_ENEMIES ; i++)
28 if (strcmp(name, defEnemy[i].name) == 0)
34 debug(("No Such Enemy '%s'\n", name));
39 Entity *getEnemy(const char *name)
41 Entity *enemy = (Entity*)map.enemyList.getHead();
43 while (enemy->next != NULL)
45 enemy = (Entity*)enemy->next;
47 if (strcmp(name, enemy->name) == 0)
53 debug(("No Such Enemy '%s'\n", name));
58 void addEnemy(const char *name, int x, int y, int flags)
60 Entity *defEnemy = getDefinedEnemy(name);
64 debug(("ERROR : COULDN'T FIND ENEMY '%s'!\n", name));
68 Entity *enemy = new Entity();
69 enemy->setName(defEnemy->name);
70 enemy->setSprites(defEnemy->sprite[0], defEnemy->sprite[1], defEnemy->sprite[2]);
71 enemy->currentWeapon = defEnemy->currentWeapon;
72 enemy->value = defEnemy->value;
73 enemy->health = defEnemy->health;
74 enemy->flags = defEnemy->flags;
77 enemy->setVelocity(0, 0);
78 enemy->baseThink = 60;
80 enemy->flags += flags;
82 enemy->reload = 120; // Wait about seconds seconds before attacking
84 if (map.data[(int)(enemy->x) >> BRICKSHIFT][(int)(enemy->y) >> BRICKSHIFT] == MAP_WATER)
86 enemy->environment = ENV_WATER;
92 bool hasClearShot(Entity *enemy)
99 Math::calculateSlope(player.x, player.y, enemy->x, enemy->y, &dx, &dy);
101 if ((dx == 0) && (dy == 0))
109 //graphics.blit(graphics.getSprite("AimedShot", true)->getCurrentFrame(), (int)(x - engine.playerPosX), (int)(y - engine.playerPosY), graphics.screen, true);
111 mx = (int)(x) >> BRICKSHIFT;
112 my = (int)(y) >> BRICKSHIFT;
114 if ((mx < 0) || (my < 0))
117 if (map.isSolid(mx, my))
120 if (Collision::collision(x, y, 3, 3, (int)player.x, (int)player.y, player.height, player.width))
127 void lookForPlayer(Entity *enemy)
130 if (player.health <= -60)
133 if (game.missionOverReason == MIS_COMPLETE)
136 // can't fire anyway!
137 if (enemy->reload > 0)
140 int x = (int)fabs(enemy->x - player.x);
141 int y = (int)fabs(enemy->y - player.y);
147 // can't even jump that high!
151 // Player is in range... go for them!
152 if (enemy->flags & ENT_ALWAYSCHASE)
154 enemy->owner->tx = (int)(player.x);
155 enemy->owner->ty = (int)(player.y);
157 else if ((Math::prand() % (35 - game.skill)) == 0)
159 enemy->owner->tx = (int)(player.x);
160 enemy->owner->ty = (int)(player.y);
163 // facing the wrong way
164 if ((enemy->face == 0) && (player.x < enemy->x))
169 // still facing the wrong way
170 if ((enemy->face == 1) && (player.x > enemy->x))
175 if (hasClearShot(enemy))
177 if (enemy->flags & ENT_ALWAYSFIRES)
179 addBullet(enemy, enemy->currentWeapon->getSpeed(enemy->face), 0);
180 if (enemy->currentWeapon == &weapon[WP_ALIENSPREAD])
182 addBullet(enemy, enemy->currentWeapon->getSpeed(enemy->face), 2);
183 addBullet(enemy, enemy->currentWeapon->getSpeed(enemy->face), -2);
186 else if ((Math::prand() % 850) <= (game.skill * 5))
188 addBullet(enemy, enemy->currentWeapon->getSpeed(enemy->face), 0);
189 if (enemy->currentWeapon == &weapon[WP_ALIENSPREAD])
191 addBullet(enemy, enemy->currentWeapon->getSpeed(enemy->face), 2);
192 addBullet(enemy, enemy->currentWeapon->getSpeed(enemy->face), -2);
196 if (enemy->flags & ENT_RAPIDFIRE)
198 if (enemy->flags & ENT_ALWAYSFIRES)
200 if ((Math::prand() % 25) > game.skill * 3)
201 Math::removeBit(&enemy->flags, ENT_ALWAYSFIRES);
205 if ((Math::prand() % 50) < game.skill * 2)
206 Math::addBit(&enemy->flags, ENT_ALWAYSFIRES);
212 if (enemy->flags & ENT_RAPIDFIRE)
213 Math::removeBit(&enemy->flags, ENT_ALWAYSFIRES);
216 if ((enemy->flags & ENT_FLIES) || (enemy->flags & ENT_SWIMS) || (enemy->flags & ENT_NOJUMP))
219 if (enemy->flags & ENT_JUMPS)
223 if ((Math::prand() % 25) == 0)
225 int distance = Math::rrand(1, 4);
226 enemy->setVelocity(distance - ((distance * 2) * enemy->face), Math::rrand(-12, -10));
233 // Jump to try and reach player (even if they are approximately level with you!)
234 if (player.y - 5 < enemy->y)
238 if ((Math::prand() % 100) == 0)
246 void doAI(Entity *enemy)
248 if (enemy->flags & ENT_GALDOV)
252 else if (enemy->flags & ENT_BOSS)
257 int x = (int)enemy->x;
258 int y = (int)enemy->y + enemy->height;
267 enemy->tx = (int)enemy->x;
269 // Don't enter areas you're not supposed to
270 if (enemy->tx != (int)enemy->x)
272 if (!(enemy->flags & (ENT_FLIES|ENT_SWIMS)))
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);
349 int old = game.currentComboHits;
353 if (old == 24 && game.currentComboHits == 25)
355 presentPlayerMedal("25_Hit_Combo");
359 void enemyBulletCollisions(Entity *bullet)
361 if (bullet->health < 1)
366 Entity *enemy = (Entity*)map.enemyList.getHead();
368 while (enemy->next != NULL)
370 enemy = (Entity*)enemy->next;
372 if ((enemy->flags & ENT_TELEPORTING) || (enemy->dead == DEAD_DYING))
377 char comboString[100];
379 if ((bullet->owner == &player) || (bullet->owner == &engine.world) || (bullet->flags & ENT_BOSS))
381 if (Collision::collision(enemy, bullet))
383 if (bullet->id != WP_LASER)
388 Math::removeBit(&bullet->flags, ENT_SPARKS);
389 Math::removeBit(&bullet->flags, ENT_PUFFS);
391 if ((enemy->flags & ENT_IMMUNE) && (!(enemy->flags & ENT_STATIC)))
393 bullet->health = 0; // include the Laser for this one!
394 enemy->owner->tx = (int)bullet->owner->x;
395 enemy->owner->ty = (int)bullet->owner->y;
396 if (enemy->x < enemy->tx) {enemy->owner->dx = 1; enemy->owner->face = 0;}
397 if (enemy->x > enemy->tx) {enemy->owner->dx = -1; enemy->owner->face = 1;}
402 Increment the bullet hits counter. The laser can only do this
403 if the target has more than 0 health. Overwise the stats screen
404 can show an accurracy of 800%. Which is just plain silly.
406 if (bullet->owner == &player)
408 enemy->tx = (int)player.x;
409 enemy->ty = (int)player.y;
412 if (player.x < enemy->x)
417 if ((bullet->id != WP_LASER) || (enemy->health > 0))
419 game.incBulletsHit();
423 if (!(enemy->flags & ENT_EXPLODES))
425 audio.playSound(SND_HIT, CH_ANY, enemy->x);
428 addBlood(enemy, bullet->dx / 4, Math::rrand(-6, -3), 1);
432 addColorParticles(bullet->x, bullet->y, Math::rrand(25, 75), -1);
437 audio.playSound(SND_CLANG, CH_ANY, enemy->x);
438 addColorParticles(bullet->x, bullet->y, Math::rrand(25, 75), -1);
441 if (enemy->health > 0)
443 if (!(enemy->flags & ENT_IMMUNE))
445 enemy->health -= bullet->damage;
448 if (enemy->health <= 0)
450 if (bullet->owner == &player)
452 addPlayerScore(enemy->value);
453 game.currentMissionEnemiesDefeated++;
455 if (player.currentWeapon != &weapon[WP_LASER])
460 snprintf(comboString, sizeof comboString, "Combo-%s", bullet->name);
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, enemy->x);
477 audio.playSound(SND_DEATH1 + Math::prand() % 3, CH_DEATH, enemy->x);
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, enemy->x);
501 audio.playSound(SND_DEATH1 + Math::prand() % 3, CH_DEATH, enemy->x);
504 if (bullet->owner == &player)
506 if (player.currentWeapon != &weapon[WP_LASER])
508 snprintf(comboString, sizeof comboString, "Combo-%s", bullet->name);
510 checkObjectives(comboString, false);
515 if (game.currentComboHits >= 3)
518 snprintf(message, sizeof message, _("%d Hit Combo!"), game.currentComboHits);
519 engine.setInfoMessage(message, 0, INFO_NORMAL);
528 int getNonGoreParticleColor(const char *name)
530 int rtn = graphics.yellow;
532 if (strcmp(name, "Pistol Blob") == 0)
534 rtn = graphics.green;
536 else if (strcmp(name, "Grenade Blob") == 0)
538 rtn = graphics.skyBlue;
540 else if (strcmp(name, "Aqua Blob") == 0)
544 else if (strcmp(name, "Laser Blob") == 0)
546 rtn = SDL_MapRGB(graphics.screen->format, 255, 0, 255);
548 else if (strcmp(name, "Machine Gun Blob") == 0)
550 rtn = SDL_MapRGB(graphics.screen->format, 200, 64, 24);
556 void gibEnemy(Entity *enemy)
558 if (enemy->flags & ENT_GALDOV)
560 addTeleportParticles(enemy->x, enemy->y, 75, SND_TELEPORT3);
561 checkObjectives("Galdov", true);
564 if (enemy->flags & ENT_EXPLODES)
566 addExplosion(enemy->x + (enemy->width / 2), enemy->y + (enemy->height / 2), 10 + (20 * game.skill), enemy);
567 addSmokeAndFire(enemy, Math::rrand(-5, 5), Math::rrand(-5, 5), 2);
572 int amount = (game.gore) ? 25 : 150;
573 int color = getNonGoreParticleColor(enemy->name);
575 for (int i = 0 ; i < amount ; i++)
577 x = enemy->x + Math::rrand(-3, 3);
578 y = enemy->y + Math::rrand(-3, 3);
582 dx = Math::rrand(-5, 5);
583 dy = Math::rrand(-15, -5);
584 addEffect(x, y, dx, dy, EFF_BLEEDS);
588 dx = Math::rrand(-5, 5);
589 dy = Math::rrand(-5, 5);
590 addColoredEffect(x, y, dx, dy, color, EFF_COLORED + EFF_WEIGHTLESS);
594 (game.gore) ? audio.playSound(SND_SPLAT, CH_ANY) : audio.playSound(SND_POP, CH_ANY, enemy->x);
599 Entity *enemy = (Entity*)map.enemyList.getHead();
600 Entity *previous = enemy;
602 map.fightingGaldov = false;
604 int x, y, absX, absY;
606 while (enemy->next != NULL)
608 enemy = (Entity*)enemy->next;
610 if (!engine.cheatBlood)
612 if (enemy->dead == DEAD_DYING)
614 if (!enemy->referenced)
616 debug(("Removing unreferenced enemy '%s'\n", enemy->name));
617 map.enemyList.remove(previous, enemy);
625 enemy->referenced = false;
630 x = (int)(enemy->x - engine.playerPosX);
631 y = (int)(enemy->y - engine.playerPosY);
636 if ((absX < 800) && (absY < 600))
639 if (enemy->flags & ENT_FLIES)
644 if (enemy->owner->flags & ENT_TELEPORTING)
650 if ((enemy->health > 0) && (!(enemy->flags & ENT_STATIC)))
654 if (enemy->owner == enemy)
660 lookForPlayer(enemy);
664 if (map.isBlizzardLevel)
666 enemy->dx += map.windPower * 0.1;
669 if (enemy->flags & ENT_NOMOVE)
676 if ((absX < 700) && (absY < 500))
678 if (enemy->flags & ENT_FIRETRAIL)
680 addFireTrailParticle(enemy->x + (enemy->face * 16) + Math::rrand(-1, 1), enemy->y + Math::rrand(-1, 1));
683 graphics.blit(enemy->getFaceImage(), x, y, graphics.screen, false);
685 if ((enemy->dx != 0) || (enemy->flags & ENT_FLIES) || (enemy->flags & ENT_STATIC))
694 if (enemy->flags & ENT_SPAWNED)
696 if ((absX > 1920) || (absY > 1440))
698 enemy->health = -100;
703 if (enemy->health > 0)
707 if ((enemy->environment == ENV_SLIME) || (enemy->environment == ENV_LAVA))
709 checkObjectives(enemy->name, false);
715 if (enemy->flags & ENT_GALDOV)
720 if (enemy->health == 0)
722 Math::removeBit(&enemy->flags, ENT_WEIGHTLESS);
723 Math::removeBit(&enemy->flags, ENT_SWIMS);
724 Math::removeBit(&enemy->flags, ENT_FLIES);
725 Math::addBit(&enemy->flags, ENT_INANIMATE);
726 Math::addBit(&enemy->flags, ENT_BOUNCES);
727 enemy->health = -1 - Math::prand() % 25;
730 if (engine.cheatBlood)
732 if (!(enemy->flags & ENT_EXPLODES))
734 if ((enemy->health % 4) == 0)
736 addBlood(enemy, Math::rrand(-2, 2), Math::rrand(-6, -3), 1);
738 else if ((enemy->health % 10) == 0)
742 audio.playSound(SND_DEATH1 + Math::prand() % 3, CH_DEATH, enemy->x);
750 if (enemy->flags & ENT_MULTIEXPLODE)
752 if (enemy->health < -30)
754 if ((enemy->health % 3) == 0)
756 addExplosion(enemy->x + Math::prand() % 25, enemy->y + Math::prand() % 25, 10 + (20 * game.skill), enemy);
757 addSmokeAndFire(enemy, Math::rrand(-5, 5), Math::rrand(-5, 5), 2);
762 if (enemy->health > -50)
768 if (enemy->flags & ENT_GALDOVFINAL)
771 enemy->dx = Math::rrand(-10, 10);
772 enemy->dy = Math::rrand(-10, 10);
776 if (enemy->dead == DEAD_ALIVE)
778 if ((absX < 800) && (absY < 600))
784 dropRandomItems((int)enemy->x, (int)enemy->y);
788 enemy->dead = DEAD_DYING;
791 if (enemy->dead == DEAD_DYING)
793 if (!enemy->referenced)
795 if ((absX < 800) && (absY < 600))
801 dropRandomItems((int)enemy->x, (int)enemy->y);
805 debug(("Removing unreferenced enemy '%s'\n", enemy->name));
806 map.enemyList.remove(previous, enemy);
814 // default the enemy to not referenced.
815 // doBullets() will change this if required.
816 enemy->referenced = false;
820 void loadEnemy(const char *token)
824 for (int i = MAX_ENEMIES - 1; i >= 0; i--)
825 if (strcmp(defEnemy[i].name, "") == 0)
830 printf("Out of enemy define space!\n");
834 char name[50], sprite[3][100], weapon[100], flags[1024];
837 sscanf(token, "%*c %[^\"] %*c %s %s %s %*c %[^\"] %*c %d %d %s", name, sprite[0], sprite[1], sprite[2], weapon, &health, &value, flags);
839 defEnemy[enemy].setName(name);
840 defEnemy[enemy].setSprites(graphics.getSprite(sprite[0], true), graphics.getSprite(sprite[1], true), graphics.getSprite(sprite[2], true));
841 defEnemy[enemy].currentWeapon = getWeaponByName(weapon);
842 defEnemy[enemy].health = health;
843 defEnemy[enemy].value = value;
845 defEnemy[enemy].flags = engine.getValueOfFlagTokens(flags);
848 void loadDefEnemies()
850 for (int i = 0 ; i < MAX_ENEMIES ; i++)
852 defEnemy[i].name[0] = 0;
857 if (!engine.loadData("data/defEnemies"))
859 return graphics.showErrorAndExit("Couldn't load enemy definitions file (%s)", "data/defEnemies");
862 char *token = strtok((char*)engine.dataBuffer, "\n");
864 char name[50], sprite[3][100], weapon[100], flags[1024];
869 if (strcmp(token, "@EOF@") == 0)
874 sscanf(token, "%*c %[^\"] %*c %s %s %s %*c %[^\"] %*c %d %d %s", name, sprite[0], sprite[1], sprite[2], weapon, &health, &value, flags);
876 defEnemy[enemy].setName(name);
877 defEnemy[enemy].setSprites(graphics.getSprite(sprite[0], true), graphics.getSprite(sprite[1], true), graphics.getSprite(sprite[2], true));
878 defEnemy[enemy].currentWeapon = getWeaponByName(weapon);
879 defEnemy[enemy].health = health;
880 defEnemy[enemy].value = value;
881 defEnemy[enemy].flags = engine.getValueOfFlagTokens(flags);
885 token = strtok(NULL, "\n");