starfighter/src/aliens.cpp

728 lines
17 KiB
C++
Raw Normal View History

/*
Copyright (C) 2003 Parallel Realities
Copyright (C) 2011, 2012 Guus Sliepen
Copyright (C) 2012, 2015 Julian Marchant
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
2015-02-26 17:20:36 +01:00
as published by the Free Software Foundation; either version 3
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
2015-02-26 17:20:36 +01:00
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
2015-02-26 17:20:36 +01:00
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Starfighter.h"
object defEnemy[MAX_DEFALIENS];
object enemy[MAX_ALIENS];
/*
This simply pulls back an alien from the array that is
"dead" (no shield) and returns the index number so we can have
a new one.
*/
static int alien_getFreeIndex()
{
for (int i = 0 ; i < engine.maxAliens ; i++)
{
if (!enemy[i].active)
{
return i;
}
}
return -1;
}
bool alien_add()
{
int index = alien_getFreeIndex();
if ((index == -1) || (currentGame.area == 23) || (currentGame.area == 26))
return 0;
signed char *alienArray;
signed char numberOfAliens = 1;
alienArray = new signed char[8];
switch(currentGame.area)
{
case 0:
case 3:
case 11:
numberOfAliens = 1;
alienArray[0] = CD_DUALFIGHTER;
break;
case 1:
case 2:
case 4:
case 5:
numberOfAliens = 2;
alienArray[0] = CD_DUALFIGHTER;
alienArray[1] = CD_PROTOFIGHTER;
break;
case 7:
case 8:
numberOfAliens = 3;
alienArray[0] = CD_DUALFIGHTER;
alienArray[1] = CD_PROTOFIGHTER;
alienArray[2] = CD_AIMFIGHTER;
break;
case 9:
// This is the mission where you need to disable cargo ships.
// Missiles are extremely bad in this mission, not because
// of the damage they do to you, but because they tend to
// accidentally destroy the cargo ships. Therefore, ships
// with missiles (dual fighters and missile boats) are
// excluded from this mission.
numberOfAliens = 2;
alienArray[0] = CD_PROTOFIGHTER;
alienArray[1] = CD_AIMFIGHTER;
break;
case 10:
case 15:
numberOfAliens = 1;
alienArray[0] = CD_ASTEROID;
break;
case 13:
case 14:
case 16:
numberOfAliens = 4;
alienArray[0] = CD_DUALFIGHTER;
alienArray[1] = CD_PROTOFIGHTER;
alienArray[2] = CD_MISSILEBOAT;
alienArray[3] = CD_AIMFIGHTER;
break;
case 18:
numberOfAliens = 2;
alienArray[0] = CD_DUALFIGHTER;
alienArray[1] = CD_MINER;
break;
case 25:
numberOfAliens = 6;
alienArray[0] = CD_DUALFIGHTER;
alienArray[1] = CD_PROTOFIGHTER;
alienArray[2] = CD_MISSILEBOAT;
alienArray[3] = CD_AIMFIGHTER;
alienArray[4] = CD_ESCORT;
alienArray[5] = CD_MOBILE_RAY;
break;
case 22:
numberOfAliens = 2;
alienArray[0] = CD_AIMFIGHTER;
alienArray[1] = CD_DUALFIGHTER;
break;
case 24:
numberOfAliens = 2;
alienArray[0] = CD_ASTEROID;
alienArray[1] = CD_ASTEROID2;
break;
case MAX_MISSIONS - 1:
numberOfAliens = 3;
alienArray[0] = CD_DUALFIGHTER;
alienArray[1] = CD_MISSILEBOAT;
alienArray[2] = CD_AIMFIGHTER;
if (currentGame.system == 2)
{
numberOfAliens = 4;
alienArray[3] = CD_PROTOFIGHTER;
}
break;
default:
numberOfAliens = 1;
alienArray[0] = CD_DUALFIGHTER;
break;
}
signed char randEnemy = alienArray[rand() % numberOfAliens];
if ((currentGame.area != 10) && (currentGame.area != 15) &&
(currentGame.area != 24))
{
if ((currentGame.system == 1) && (currentGame.area == MAX_MISSIONS - 1))
{
if ((rand() % 5) == 0)
randEnemy = CD_SLAVETRANSPORT;
}
if ((currentGame.area != MAX_MISSIONS - 1) &&
((currentGame.maxPlasmaRate > currentGame.minPlasmaRate) ||
(currentGame.maxPlasmaOutput > currentGame.minPlasmaOutput) ||
(currentGame.maxPlasmaDamage > currentGame.minPlasmaDamage)))
{
if ((rand() % 6) == 0)
randEnemy = CD_TRANSPORTSHIP;
}
}
delete[] alienArray;
enemy[index] = defEnemy[randEnemy];
enemy[index].active = true;
enemy[index].face = rand() % 2;
enemy[index].owner = &enemy[index]; // Most enemies will own themselves
enemy[index].target = &enemy[index];
enemy[index].thinktime = (50 + rand() % 50);
enemy[index].systemPower = enemy[index].maxShield;
enemy[index].deathCounter = 0 - (enemy[index].maxShield * 3);
enemy[index].hit = 0;
limitInt(&enemy[index].deathCounter, -250, 0);
// Attempts to place an alien. If it fails, the alien is deactivated.
for (int i = 0 ; i < 100 ; i++)
{
if (alien_place(&enemy[index]))
break;
enemy[index].active = false;
2011-08-26 16:55:46 +02:00
return false;
}
if (enemy[index].classDef == CD_CARGOSHIP)
addCargo(&enemy[index], P_CARGO);
if (enemy[index].classDef == CD_MOBILE_RAY)
enemy[index].shield = 25;
if (enemy[index].classDef == CD_ESCORT)
enemy[index].shield = 50;
enemy[index].dx = rrand(-2, 2);
enemy[index].dy = rrand(-2, 2);
enemy[index].ammo[0] = 0;
if (currentGame.area == 18)
2011-09-04 14:23:31 +02:00
enemy[index].flags |= FL_HASMINIMUMSPEED;
2011-08-26 16:55:46 +02:00
return true;
}
2015-03-07 05:18:31 +01:00
void alien_addDrone(object *hostAlien)
{
int index = alien_getFreeIndex();
if (index == -1)
return;
enemy[index] = defEnemy[CD_DRONE];
enemy[index].active = true;
enemy[index].face = rand() % 2;
enemy[index].owner = &enemy[index]; // Most enemies will own themselves
enemy[index].target = &enemy[index];
enemy[index].thinktime = (50 + rand() % 50);
enemy[index].systemPower = enemy[index].maxShield;
enemy[index].deathCounter = 0 - (enemy[index].maxShield * 3);
enemy[index].hit = 0;
2015-03-07 05:18:31 +01:00
enemy[index].x = hostAlien->x + rand() % 50;
enemy[index].y = hostAlien->y + rand() % 50;
}
2015-03-07 05:18:31 +01:00
void alien_addSmallAsteroid(object *hostAlien)
{
if (engine.missionCompleteTimer != 0)
return;
int index = -1;
int debris = 1 + rand() % 10;
for (int i = 0 ; i < debris ; i++)
2015-03-07 05:18:31 +01:00
addBullet(&weapon[W_ROCKETS], hostAlien, 0, 0);
for (int i = 10 ; i < 20 ; i++)
if (!enemy[i].active)
index = i;
if (index == -1)
return;
if ((rand() % 10) > 3)
{
enemy[index] = defEnemy[CD_ASTEROID2];
enemy[index].imageIndex[0] = enemy[index].imageIndex[1] = 39 + rand() % 2;
enemy[index].image[0] = shipShape[enemy[index].imageIndex[0]];
enemy[index].image[1] = shipShape[enemy[index].imageIndex[1]];
}
else
{
enemy[index] = defEnemy[CD_DRONE];
}
enemy[index].owner = &enemy[index]; // Most enemies will own themselves
enemy[index].target = &enemy[index];
enemy[index].thinktime = 1;
enemy[index].systemPower = enemy[index].maxShield;
enemy[index].deathCounter = 0 - (enemy[index].maxShield * 3);
enemy[index].hit = 0;
2015-03-07 05:18:31 +01:00
enemy[index].x = hostAlien->x;
enemy[index].y = hostAlien->y;
enemy[index].active = true;
}
2015-03-07 05:18:31 +01:00
void alien_addFriendly(int type)
{
if (type != FR_SID)
enemy[type] = defEnemy[CD_FRIEND];
else
enemy[type] = defEnemy[CD_SID];
enemy[type].owner = &enemy[type];
enemy[type].target = &enemy[type];
enemy[type].active = true;
if (rand() % 2 == 0)
enemy[type].x = rrand((int)(screen->w / 2), (int)(screen->w / 2) + 150);
else
enemy[type].x = rrand((int)(screen->w / 2) - 150, (int)(screen->w / 2));
if (rand() % 2 == 0)
enemy[type].y = rrand((int)(screen->h / 2), (int)(screen->h / 2) + 150);
else
enemy[type].y = rrand((int)(screen->h / 2) - 150, (int)(screen->h / 2));
if (type == FR_PHOEBE)
enemy[type].classDef = CD_PHOEBE;
if (type == FR_URSULA)
enemy[type].classDef = CD_URSULA;
// For the sake of it being the final battle :)
if (currentGame.area == 25)
enemy[type].flags |= FL_IMMORTAL;
}
2015-03-07 05:18:31 +01:00
bool alien_place(object *alien)
{
2015-03-07 05:18:31 +01:00
if (rand() % 2 == 0)
alien->x = rrand(screen->w, screen->w * 2);
else
alien->x = rrand(-screen->w, 0);
2015-03-07 05:18:31 +01:00
if (rand() % 2 == 0)
alien->y = rrand(screen->h, screen->h * 2);
else
alien->y = rrand(-screen->h, 0);
2015-03-07 05:18:31 +01:00
if (currentGame.area == 24)
{
2015-03-07 05:18:31 +01:00
alien->x = screen->w;
alien->y = rrand(screen->h / 3, (2 * screen->h) / 3);
}
2015-03-07 05:18:31 +01:00
for (int i = 0 ; i < MAX_ALIENS ; i++)
{
if ((enemy[i].owner != alien) && (enemy[i].shield > 0))
{
2015-03-07 05:18:31 +01:00
if (collision(alien->x, alien->y, alien->image[0]->w, alien->image[0]->h, enemy[i].x, enemy[i].y, enemy[i].image[0]->w, enemy[i].image[0]->h))
return false;
}
2015-03-07 05:18:31 +01:00
}
2015-03-07 05:18:31 +01:00
return true;
}
2015-03-07 05:18:31 +01:00
void alien_setAI(object *alien)
{
// Make friendly craft generally concentrate on smaller fighters
if ((alien->flags & FL_FRIEND) && (alien->target == &enemy[WC_BOSS]))
{
if ((rand() % 5) == 0)
{
2015-03-07 05:18:31 +01:00
alien->target = alien;
alien->thinktime = 0;
return;
}
2015-03-07 05:18:31 +01:00
}
2015-03-07 05:18:31 +01:00
int i = rand() % 10;
float tx = alien->target->x;
float ty = alien->target->y;
2015-03-07 05:18:31 +01:00
int chase = 0; // Chance in 10 of chasing player
int area = 0; // Chance in 10 of moving to an area around the player
int stop = 0; // Chance in 10 of hanging back
int point = 0; // Size of area alien will move into
2015-03-07 05:18:31 +01:00
switch (alien->AIType)
{
case AI_NORMAL:
chase = 3;
point = 6;
stop = 9;
area = 250;
break;
case AI_OFFENSIVE:
chase = 7;
point = 8;
stop = 9;
area = 50;
break;
case AI_DEFENSIVE:
chase = 2;
point = 6;
stop = 8;
area = 300;
break;
case AI_EVASIVE:
chase = 1;
point = 8;
stop = 9;
area = 600;
break;
case AI_WANDER:
chase = -1;
point = 0;
stop = 10;
area = 1200;
break;
}
2015-03-07 05:18:31 +01:00
if (i <= chase)
{
2015-03-07 05:18:31 +01:00
// Chase the target
alien->dx = ((alien->x - tx) / ((300 / alien->speed) + rand() % 100));
alien->dy = ((alien->y - ty) / ((300 / alien->speed) + rand() % 100));
return;
}
2015-03-07 05:18:31 +01:00
else if ((i >= point) && (i <= stop))
{
2015-03-07 05:18:31 +01:00
// Fly to a random point around the target
tx += (rand() % area - (rand() % area * 2));
ty += (rand() % area - (rand() % area * 2));
alien->dx = ((alien->x - tx) / ((300 / alien->speed) + rand() % 100));
alien->dy = ((alien->y - ty) / ((300 / alien->speed) + rand() % 100));
return;
}
2015-03-07 05:18:31 +01:00
else
{
2015-03-07 05:18:31 +01:00
// Hang back
alien->dx = 0;
alien->dy = 0;
return;
}
}
2015-03-07 05:18:31 +01:00
void alien_setKlineAttackMethod(object *alien)
{
2015-03-07 05:18:31 +01:00
alien->maxShield -= 500;
if (alien->maxShield == 0)
alien->flags &= ~FL_CANNOTDIE;
2015-03-07 05:18:31 +01:00
if (alien->maxShield == 1000)
{
2015-03-07 05:18:31 +01:00
setRadioMessage(FACE_KLINE, "Very good, Bainfield. Now let's get a little more serious...", 1);
alien->weaponType[0] = W_SPREADSHOT;
alien->chance[1] = 40;
}
2015-03-07 05:18:31 +01:00
else if (alien->maxShield == 500)
{
2015-03-07 05:18:31 +01:00
setRadioMessage(FACE_KLINE, "Your ability to stay alive irritates me!! Try dodging some of these!!", 1);
alien->weaponType[0] = W_DIRSHOCKMISSILE;
alien->weaponType[1] = W_DIRSHOCKMISSILE;
alien->chance[0] = 2;
alien->chance[1] = 2;
alien->flags |= FL_AIMS;
}
2015-03-07 05:18:31 +01:00
else if (alien->maxShield == 0)
{
2015-03-07 05:18:31 +01:00
setRadioMessage(FACE_KLINE, "ENOUGH!! THIS ENDS NOW!!!", 1);
alien->weaponType[0] = W_AIMED_SHOT;
alien->weaponType[1] = W_MICRO_HOMING_MISSILES;
alien->flags |= FL_CANCLOAK;
alien->chance[0] = 100;
alien->chance[1] = 2;
}
2015-03-07 05:18:31 +01:00
alien->shield = 500;
}
/*
This AI is exclusively for Kline.
*/
void alien_setKlineAI(object *alien)
{
// Weapon type change
if ((rand() % 3) == 0)
{
2015-03-07 05:18:31 +01:00
if (currentGame.area != 26)
{
2015-03-07 05:18:31 +01:00
alien->flags &= ~FL_AIMS;
2015-03-07 05:18:31 +01:00
switch(rand() % 2)
{
2015-03-07 05:18:31 +01:00
case 0:
alien->weaponType[0] = W_TRIPLE_SHOT;
break;
case 1:
alien->weaponType[0] = W_AIMED_SHOT;
alien->flags |= FL_AIMS;
break;
}
}
}
2015-03-07 05:18:31 +01:00
alien->flags &= ~(FL_CIRCLES | FL_CONTINUOUS_FIRE | FL_DROPMINES);
2015-03-07 05:18:31 +01:00
switch(rand() % 10)
{
2015-03-07 05:18:31 +01:00
case 0:
if ((alien->weaponType[0] != W_DIRSHOCKMISSILE) && (alien->weaponType[1] != W_MICRO_HOMING_MISSILES))
alien->flags |= FL_CONTINUOUS_FIRE;
alien->dx = ((alien->x - alien->target->x) / ((300 / alien->speed) + rand() % 100));
alien->dy = ((alien->y - alien->target->y) / ((300 / alien->speed) + rand() % 100));
break;
2015-03-07 05:18:31 +01:00
case 1:
case 2:
// Kline only attacks then he is ready!
if ((!(alien->flags & FL_NOFIRE)) && (currentGame.area == 11))
alien->flags |= FL_DROPMINES;
break;
2015-03-07 05:18:31 +01:00
case 3:
case 4:
alien->flags |= FL_CIRCLES;
break;
default:
2015-03-07 05:18:31 +01:00
alien_setAI(alien);
break;
}
}
/*
"Looks" for an enemy by picking a randomly active enemy and using them
as a target. If the target is too far away, it will be ignored.
*/
2015-03-07 05:18:31 +01:00
void alien_searchForTarget(object *alien)
{
int i;
if (alien->flags & FL_WEAPCO)
{
i = (rand() % 10);
if (i == 0)
{
alien->target = &player;
return;
}
}
i = rand() % MAX_ALIENS;
object *targetEnemy = &enemy[i];
// Tell Sid not to attack craft that are already disabled or can
// return fire. This will save him from messing about (unless we're on the last mission)
if ((alien->classDef == CD_SID) && (currentGame.area != 25))
{
if ((targetEnemy->flags & FL_DISABLED) || (!(targetEnemy->flags & FL_NOFIRE)))
return;
}
// Tell Phoebe and Ursula not to attack ships that cannot fire or are disabled (unless we're on the last mission)
if (currentGame.area != 25)
{
if ((alien->classDef == CD_PHOEBE) || (alien->classDef == CD_URSULA))
{
// Don't attack the boss or we could be here all day(!)
if (targetEnemy->classDef == CD_BOSS)
return;
if ((targetEnemy->flags & FL_DISABLED) || (targetEnemy->flags & FL_NOFIRE))
return;
}
}
if ((targetEnemy->shield < 1) || (!targetEnemy->active))
return;
if ((targetEnemy->flags & FL_WEAPCO) && (alien->flags & FL_WEAPCO))
return;
if ((targetEnemy->flags & FL_FRIEND) && (alien->flags & FL_FRIEND))
return;
if (abs((int)alien->x - (int)alien->target->x) > 550)
return;
if (abs((int)alien->y - (int)alien->target->y) > 400)
return;
alien->target = targetEnemy;
}
2015-03-07 05:18:31 +01:00
/*
Do various checks to see if the alien can fire at the target.
*/
int alien_checkTarget(object *alien)
{
// No target
if (alien->target == alien)
return 0;
// Whilst firing a Ray, no other weapons can be fired!
if (alien->flags & FL_FIRERAY)
return 0;
// The target is on the same side as you!
if ((alien->flags & FL_WEAPCO) && (alien->target->flags & FL_WEAPCO))
return 0;
if ((alien->flags & FL_FRIEND) && (alien->target->flags & FL_FRIEND))
return 0;
// You're facing the wrong way
if ((alien->face == 0) && (alien->target->x < alien->x))
return 0;
if ((alien->face == 1) && (alien->target->x > alien->x))
return 0;
// Slightly more than half a screen away from you
if (abs((int)alien->x - (int)alien->target->x) > 550)
return 0;
if ((alien->flags & FL_AIMS) || (alien->flags & FL_CONTINUOUS_FIRE))
return 1;
// Not at the correct vertical height
if ((alien->y < alien->target->y - 15) || (alien->y > alien->target->y + alien->target->image[0]->h + 15))
return 0;
return 1;
}
/*
Currently only used for the allies. Whilst flying around, the allies will fire on
any enemy craft that enter their line of sight.
*/
2015-03-07 05:18:31 +01:00
int alien_enemiesInFront(object *alien)
{
object *anEnemy = enemy;
for (int i = 0 ; i < MAX_ALIENS ; i++)
{
if ((alien != anEnemy) && (anEnemy->flags & FL_WEAPCO) && (anEnemy->shield > 0))
{
if ((alien->y > anEnemy->y - 15) && (alien->y < anEnemy->y + anEnemy->image[0]->h + 15))
{
if ((alien->face == 1) && (anEnemy->x < alien->x))
return 1;
if ((alien->face == 0) && (anEnemy->x > alien->x))
return 1;
}
}
anEnemy++;
}
return 0;
}
2015-03-07 05:18:31 +01:00
void alien_move(object *alien)
{
bool checkCollisions;
if ((alien->flags & FL_LEAVESECTOR) || (alien->shield < 1))
2011-08-26 16:55:46 +02:00
checkCollisions = false;
else
checkCollisions = true;
if (alien->owner == alien)
{
if (alien->flags & FL_CIRCLES)
{
if (alien->face == 0)
{
alien->dx += 0.02;
alien->dy += 0.02;
}
else
{
alien->dx -= 0.02;
alien->dy -= 0.02;
}
alien->x -= (sinf(alien->dx) * 4);
alien->y -= (cosf(alien->dy) * 4);
}
else
{
alien->x -= alien->dx;
alien->y -= alien->dy;
}
}
object *anEnemy = enemy;
if (checkCollisions)
{
for (int i = 0 ; i < MAX_ALIENS ; i++)
{
if ((alien->flags & FL_LEAVESECTOR) || (alien->classDef == CD_DRONE) || (alien->classDef == CD_ASTEROID2) || (alien->owner == anEnemy->owner) || (alien->owner->owner == anEnemy->owner) || (anEnemy->shield < 1))
{
anEnemy++;
continue;
}
if (collision(alien, anEnemy))
{
if ((anEnemy->classDef == CD_BARRIER) && (anEnemy->owner != alien))
{
alien->shield--;
alien->hit = 3;
alien->dx *= -1;
alien->dy *= -1;
playSound(SFX_HIT, alien->x);
}
}
anEnemy++;
}
}
// Handle a collision with the player
if ((player.shield > 0) && (alien->shield > 0) && (checkCollisions))
{
if (collision(alien, &player))
{
if (alien->classDef == CD_ASTEROID)
{
if (!engine.cheatShield)
player.shield -= alien->shield;
alien->shield = 0;
playSound(SFX_EXPLOSION, alien->x);
setInfoLine("Warning: Asteroid Collision Damage!!", FONT_RED);
player.hit = 5;
playSound(SFX_HIT, player.x);
}
if (alien->classDef == CD_ASTEROID2)
{
if (!engine.cheatShield)
player.shield -= alien->shield;
alien->shield = 0;
playSound(SFX_EXPLOSION, alien->x);
setInfoLine("Warning: Asteroid Collision Damage!!", FONT_RED);
player.hit = 5;
playSound(SFX_HIT, player.x);
}
if (alien->classDef == CD_BARRIER)
{
if (!engine.cheatShield)
player.shield--;
player.hit = 5;
playSound(SFX_HIT, player.x);
}
}
}
}