starfighter/src/bullet.cpp

492 lines
12 KiB
C++
Raw Normal View History

/*
Copyright (C) 2003 Parallel Realities
Copyright (C) 2011, 2012 Guus Sliepen
Copyright (C) 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"
2015-03-11 20:36:42 +01:00
void bullet_add(object *theWeapon, object *attacker, int y, int dy)
{
object *bullet;
signed char imageIndex;
int tempX, tempY, steps;
bullet = new object;
if (attacker == &player)
currentGame.shots++;
bullet->next = NULL;
bullet->active = true;
bullet->x = attacker->x + (attacker->image[0]->w / 2) -
(theWeapon->image[0]->w * attacker->face);
bullet->y = attacker->y + y;
bullet->flags = theWeapon->flags;
bullet->shield = 300; // bullets live for (approximately) 5 seconds
// Timed explosions live between 1 and 3 seconds
if (bullet->flags & WF_TIMEDEXPLOSION)
bullet->shield = 60 + ((rand() % 3) * 60);
if (attacker->face == 0)
{
bullet->dx = theWeapon->speed;
if ((currentGame.area == 18) || (currentGame.area == 24))
bullet->dx += fabsf(engine.ssx + engine.smx);
}
else
{
bullet->dx = (0 - theWeapon->speed);
}
if (bullet->flags & WF_VARIABLE_SPEED)
{
bullet->dx = rrand(100, 200);
bullet->dx /= 10;
if (attacker->face == 1)
bullet->dx = 0 - bullet->dx;
}
bullet->dy = dy;
if (bullet->flags & WF_SCATTER)
{
bullet->dy = rrand(-200, 200);
if (bullet->dy != 0)
bullet->dy /= 200;
}
if (attacker->flags & FL_WEAPCO)
2011-09-04 14:23:31 +02:00
bullet->flags |= WF_WEAPCO;
else
2011-09-04 14:23:31 +02:00
bullet->flags |= WF_FRIEND;
bullet->owner = attacker->owner;
bullet->id = theWeapon->id;
bullet->damage = theWeapon->damage;
if (bullet->id == WT_CHARGER)
{
bullet->damage = attacker->ammo[1] / 2;
if (bullet->damage < 15)
{
bullet->damage = 1;
bullet->id = WT_PLASMA;
}
}
bullet->target = NULL;
if (attacker->flags & FL_FRIEND)
imageIndex = 0;
else
imageIndex = 1;
// Use the enemy's images if applicable
if (bullet->id != WT_ROCKET)
bullet->image[0] = theWeapon->image[imageIndex];
else
bullet->image[0] = theWeapon->image[attacker->face];
if (bullet->flags & WF_AIMED)
{
tempX = (int)fabsf(attacker->target->x - attacker->x);
tempY = (int)fabsf(attacker->target->y - attacker->y);
steps = max(tempX, tempY);
if (steps < 12)
steps = 12;
if (!(bullet->flags & WF_TIMEDEXPLOSION))
steps /= 8;
else
steps /= 6 + (rand() % 6);
tempX = (int)(attacker->target->x - attacker->x);
tempY = (int)(attacker->target->y - attacker->y);
bullet->dx = tempX / steps;
bullet->dy = tempY / steps;
}
if (attacker->classDef == CD_ASTEROID)
{
bullet->dx = rrand(-20, 20);
bullet->dy = rrand(-20, 20);
bullet->image[0] = shape[4];
}
engine.bulletTail->next = bullet;
engine.bulletTail = bullet;
}
/*
Used for homing missiles. When a missile is active and it is told to home in
on an enemy, it will attempt to randomly grab one every frame if it does not
already have a target. If the target it is currently chasing is killed, it will
begin to look for a new one (done in doBullets()). The homing missile will make
one attempt per call (one call per frame) to find a suitable target. If the target
it picks is dead or outside the screen range, then it returns NULL. A suitable
target will be returned as the object address.
*/
static object *getRandomEnemy(object *bullet)
{
int i;
if (bullet->owner->flags & FL_WEAPCO)
{
i = (rand() % 10);
if (i < 1)
return &player;
}
i = rand() % ALIEN_MAX;
if ((aliens[i].shield < 1) || (!aliens[i].active))
return NULL;
if (aliens[i].flags & FL_IMMORTAL)
return NULL;
if ((bullet->owner->flags & FL_WEAPCO) && (aliens[i].flags & FL_WEAPCO))
return NULL;
if ((bullet->owner->flags & FL_FRIEND) && (aliens[i].flags & FL_FRIEND))
return NULL;
if (abs((int)bullet->x - (int)aliens[i].target->x) > 800)
return NULL;
if (abs((int)bullet->y - (int)aliens[i].target->y) > 200)
return NULL;
return &aliens[i];
}
2011-08-24 22:22:26 +02:00
char checkPlayerShockDamage(float x, float y)
{
// Don't let the player be hurt by an explosion after they have completed
// all the mission objectives. That would be *really* annoying!
if ((engine.cheatShield) || (engine.missionCompleteTimer != 0))
return 0;
float distX = fabsf(x - player.x);
float distY = fabsf(y - player.y);
if ((distX <= 50) && (distY <= 50))
{
if (distX >= 1)
distX /= 5;
if (distY >= 1)
distY /= 5;
player.shield -= (int)(10 - distX);
player.shield -= (int)(10 - distY);
limitInt(&player.shield, 0, player.maxShield);
player.hit = 10;
return 1;
}
return 0;
}
/*
This handles active bullets in a linked list. The current bullet and
previous bullet pointers are first assigned to the main header bullet
and each successive bullet is pulled out. Bullets are moved in their
delta coordinates, with rockets having fire trails added to them. Seperate
collision checks are done for a bullet that belongs to the enemy and one
that belongs to a player. However rockets can hit anyone. Upon an enemy
being killed, mission requirements are checked and collectables are randomly
spawned.
*/
void doBullets()
{
object *bullet = engine.bulletHead;
object *prevBullet = engine.bulletHead;
engine.bulletTail = engine.bulletHead;
object *alien, *theCargo;
2011-08-26 16:55:46 +02:00
bool okayToHit = false;
int old_shield;
float homingMissileSpeed = 0;
while (bullet->next != NULL)
{
bullet = bullet->next;
if (bullet->active)
{
if (bullet->flags & WF_HOMING)
{
if (bullet->target == NULL)
bullet->target = getRandomEnemy(bullet);
if (bullet->owner->flags & FL_FRIEND)
homingMissileSpeed = 0.25;
else
homingMissileSpeed = 0.05;
}
if (bullet->id == WT_ROCKET)
{
addExplosion(bullet->x, bullet->y, E_SMALL_EXPLOSION);
}
else if (bullet->id == WT_MICROROCKET)
{
addExplosion(bullet->x, bullet->y, E_TINY_EXPLOSION);
}
2011-09-05 21:55:23 +02:00
if ((bullet->flags & WF_AIMED))
{
blit(bullet->image[0], (int)(bullet->x - bullet->dx),
(int)(bullet->y - bullet->dy));
}
if (bullet->id == WT_CHARGER)
{
for (int i = 0 ; i < bullet->damage * 2 ; i++)
blit(bullet->image[0],
(int)(bullet->x - rrand(-(bullet->damage * 2 / 3), 0)),
(int)(bullet->y + rrand(-3, 3)));
}
blit(bullet->image[0], (int)bullet->x, (int)bullet->y);
bullet->x += bullet->dx;
bullet->y += bullet->dy;
if (bullet->target != NULL)
{
if (bullet->x < bullet->target->x)
limitFloat(&(bullet->dx += homingMissileSpeed), -15, 15);
if (bullet->x > bullet->target->x)
limitFloat(&(bullet->dx -= homingMissileSpeed), -15, 15);
//Rocket is (more or less) inline with target. Fly straight
if ((bullet->x > bullet->target->x - 1) && (bullet->x < bullet->target->x + 5))
bullet->dx = 0;
if (bullet->y < bullet->target->y)
limitFloat(&(bullet->dy += homingMissileSpeed), -15, 15);
if (bullet->y > bullet->target->y)
limitFloat(&(bullet->dy -= homingMissileSpeed), -15, 15);
//Rocket is (more or less) inline with target. Fly straight
if ((bullet->y > bullet->target->y - 1) && (bullet->y < bullet->target->y + 5))
bullet->dy = 0;
if ((bullet->target->shield < 1) || (bullet->target->flags & FL_ISCLOAKED))
bullet->target = NULL;
}
bullet->x += engine.ssx + engine.smx;
bullet->y += engine.ssy + engine.smy;
for (int i = 0 ; i < ALIEN_MAX ; i++)
{
alien = &aliens[i];
if ((alien->shield < 1) || (!alien->active))
continue;
2011-08-26 16:55:46 +02:00
okayToHit = false;
if ((bullet->flags & WF_FRIEND) && (alien->flags & FL_WEAPCO))
2011-08-26 16:55:46 +02:00
okayToHit = true;
if ((bullet->flags & WF_WEAPCO) && (alien->flags & FL_FRIEND))
2011-08-26 16:55:46 +02:00
okayToHit = true;
if ((bullet->id == WT_ROCKET) || (bullet->id == WT_LASER) ||
(bullet->id == WT_CHARGER))
2011-08-26 16:55:46 +02:00
okayToHit = true;
if (bullet->owner == alien->owner)
2011-08-26 16:55:46 +02:00
okayToHit = false;
if (okayToHit)
{
if ((bullet->active) && (collision(bullet, alien)))
{
old_shield = alien->shield;
if (bullet->owner == &player)
{
currentGame.hits++;
if ((alien->classDef == CD_PHOEBE) ||
(alien->classDef == CD_URSULA))
getMissFireMessage(alien);
}
if (!(alien->flags & FL_IMMORTAL))
{
alien_hurt(alien, bullet->owner, bullet->damage,
(bullet->flags & WF_DISABLE));
alien->hit = 5;
}
if (bullet->id == WT_CHARGER)
{
bullet->damage -= old_shield;
if (bullet->damage <= 0)
{
bullet->active = false;
bullet->shield = 0;
audio_playSound(SFX_EXPLOSION, bullet->x);
for (int i = 0 ; i < 10 ; i++)
addExplosion(bullet->x + rrand(-35, 35),
bullet->y + rrand(-35, 35),
E_BIG_EXPLOSION);
}
}
else
{
bullet->active = false;
bullet->shield = 0;
}
if (bullet->id == WT_ROCKET)
addExplosion(bullet->x, bullet->y, E_BIG_EXPLOSION);
else
addExplosion(bullet->x, bullet->y, E_SMALL_EXPLOSION);
}
}
}
// Check for bullets hitting player
if ((bullet->flags & WF_WEAPCO) || (bullet->id == WT_ROCKET) ||
(bullet->id == WT_LASER) || (bullet->id == WT_CHARGER))
{
if (bullet->active && (player.shield > 0) &&
(bullet->owner != &player) && collision(bullet, &player))
{
old_shield = player.shield;
if ((!engine.cheatShield) || (engine.missionCompleteTimer != 0))
{
if ((player.shield > engine.lowShield) &&
(player.shield - bullet->damage <= engine.lowShield))
setInfoLine("!!! WARNING: SHIELD LOW !!!", FONT_RED);
player.shield -= bullet->damage;
limitInt(&player.shield, 0, player.maxShield);
player.hit = 5;
}
if ((bullet->owner->classDef == CD_PHOEBE) ||
(bullet->owner->classDef == CD_URSULA))
getPlayerHitMessage(bullet->owner);
if (bullet->id == WT_CHARGER)
{
bullet->damage -= old_shield;
if (bullet->damage <= 0)
{
bullet->active = false;
bullet->shield = 0;
audio_playSound(SFX_EXPLOSION, bullet->x);
for (int i = 0 ; i < 10 ; i++)
addExplosion(bullet->x + rrand(-35, 35),
bullet->y + rrand(-35, 35), E_BIG_EXPLOSION);
}
}
else
{
bullet->active = false;
bullet->shield = 0;
}
2015-03-08 15:38:58 +01:00
audio_playSound(SFX_HIT, player.x);
if (bullet->id == WT_ROCKET)
addExplosion(bullet->x, bullet->y, E_BIG_EXPLOSION);
else
addExplosion(bullet->x, bullet->y, E_SMALL_EXPLOSION);
}
}
}
if ((currentGame.difficulty > DIFFICULTY_EASY) &&
((bullet->owner == &player) || (bullet->id == WT_ROCKET)))
{
for (int j = 0 ; j < 20 ; j++)
{
theCargo = &cargo[j];
if (theCargo->active)
{
if (collision(bullet, theCargo))
{
bullet->active = false;
addExplosion(bullet->x, bullet->y, E_SMALL_EXPLOSION);
2015-03-08 15:38:58 +01:00
audio_playSound(SFX_HIT, theCargo->x);
if (theCargo->collectType != P_PHOEBE)
{
theCargo->active = false;
2015-03-08 15:38:58 +01:00
audio_playSound(SFX_EXPLOSION, theCargo->x);
for (int i = 0 ; i < 10 ; i++)
addExplosion(theCargo->x + rrand(-15, 15),
theCargo->y + rrand(-15, 15),
E_BIG_EXPLOSION);
updateMissionRequirements(M_PROTECT_PICKUP,
P_CARGO, 1);
}
}
}
}
}
// check to see if a bullet (on any side) hits a mine
checkMineBulletCollisions(bullet);
bullet->shield--;
if (bullet->shield < 1)
{
if (bullet->flags & WF_TIMEDEXPLOSION)
{
2015-03-08 15:38:58 +01:00
audio_playSound(SFX_EXPLOSION, bullet->x);
for (int i = 0 ; i < 10 ; i++)
addExplosion(bullet->x + rrand(-35, 35),
bullet->y + rrand(-35, 35), E_BIG_EXPLOSION);
if (checkPlayerShockDamage(bullet->x, bullet->y))
setInfoLine("Warning: Missile Shockwave Damage!!",
FONT_RED);
}
bullet->active = false;
}
if (bullet->active)
{
prevBullet = bullet;
engine.bulletTail = bullet;
}
else
{
prevBullet->next = bullet->next;
delete bullet;
bullet = prevBullet;
}
}
}