Files
commandergenius/project/jni/application/alienblaster/enemy.cpp

538 lines
16 KiB
C++

/***************************************************************************
alienBlaster
Copyright (C) 2004
Paul Grathwohl, Arne Hormann, Daniel Kuehn, Soenke Schwardt
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, 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
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
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
***************************************************************************/
#include "enemy.h"
#include "SDL.h"
#include "surfaceDB.h"
#include "boundingBox.h"
#include "mixer.h"
#include "shots.h"
#include "shot.h"
#include "racers.h"
#include "racer.h"
#include "items.h"
#include "item.h"
#include "wrecks.h"
#include "wreck.h"
#include "global.h"
#include "explosions.h"
#include "explosion.h"
#include "options.h"
Enemy::Enemy( Vector2D pos, Vector2D vel, EnemyTypes whichEnemyType,
bool isInFormation, bool fireByFormation ) {
this->isInFormation = isInFormation;
this->fireByFormation = fireByFormation;
enemyType = whichEnemyType;
hitpoints = ENEMY_HITPOINTS[ enemyType ];
this->pos = pos;
this->vel = vel;
relTargetPos = Vector2D(0,0);
switch ( enemyType ) {
case FIGHTER:
{
string fn = LVL_ENEMY_FIGHTER;
levelConf->getStr( LVL_ENEMY_FIGHTER, fn );
spriteEnemy = surfaceDB.loadSurface( fn );
fn = LVL_ENEMY_FIGHTER_SHADOW;
levelConf->getStr( LVL_ENEMY_FIGHTER_SHADOW, fn );
spriteShadow = surfaceDB.loadSurface( fn, true );
break;
}
case BOMBER:
{
string fn = LVL_ENEMY_BOMBER;
levelConf->getStr( LVL_ENEMY_BOMBER, fn );
spriteEnemy = surfaceDB.loadSurface( fn );
fn = LVL_ENEMY_BOMBER_SHADOW;
levelConf->getStr( LVL_ENEMY_BOMBER_SHADOW, fn );
spriteShadow = surfaceDB.loadSurface( fn, true );
break;
}
case TANK:
{
string fn = LVL_ENEMY_TANK;
levelConf->getStr( LVL_ENEMY_TANK, fn );
spriteEnemy = surfaceDB.loadSurface( fn );
break;
}
case BOSS_1_MAIN_GUN:
{
string fn = LVL_ENEMY_BOSS_1_MAIN_GUN;
levelConf->getStr( LVL_ENEMY_BOSS_1_MAIN_GUN, fn );
spriteEnemy = surfaceDB.loadSurface( fn );
break;
}
case BOSS_1_ROCKET_LAUNCHER:
{
string fn = LVL_ENEMY_BOSS_1_ROCKET_LAUNCHER;
levelConf->getStr( LVL_ENEMY_BOSS_1_ROCKET_LAUNCHER, fn );
spriteEnemy = surfaceDB.loadSurface( fn );
break;
}
case BOSS_1_SHOT_BATTERY_LEFT:
{
string fn = LVL_ENEMY_BOSS_1_SHOT_BATTERY_LEFT;
levelConf->getStr( LVL_ENEMY_BOSS_1_SHOT_BATTERY_LEFT, fn );
spriteEnemy = surfaceDB.loadSurface( fn );
break;
}
case BOSS_1_SHOT_BATTERY_RIGHT:
{
string fn = LVL_ENEMY_BOSS_1_SHOT_BATTERY_RIGHT;
levelConf->getStr( LVL_ENEMY_BOSS_1_SHOT_BATTERY_RIGHT, fn );
spriteEnemy = surfaceDB.loadSurface( fn );
break;
}
case BOSS_2:
{
spriteEnemy = surfaceDB.loadSurface( FN_ENEMY_BOSS_2 );
spriteShadow = surfaceDB.loadSurface( FN_ENEMY_BOSS_2_SHADOW, true );
boss2PointReached = false;
boss2TargetPos = Vector2D(SCREEN_WIDTH / 2, 100);
break;
}
default:
{
string fn = LVL_ENEMY_FIGHTER;
levelConf->getStr( LVL_ENEMY_FIGHTER, fn );
spriteEnemy = surfaceDB.loadSurface( fn );
break;
}
}
boundingBox = new BoundingBox( lroundf(pos.getX() - spriteEnemy->w * 0.45),
lroundf(pos.getY() - spriteEnemy->h * 0.45),
lroundf(spriteEnemy->w * 0.9),
lroundf(spriteEnemy->h * 0.9) );
nextShotPrimary = rand() % (ENEMY_RAND_WAIT_PRIMARY[ enemyType ]+1);
nextShotSecondary = rand() % (ENEMY_RAND_WAIT_SECONDARY[ enemyType ]+1);
sndShotPrimary = Mixer::mixer().loadSample( FN_SOUND_SHOT_PRIMARY );
sndShotSecondary = Mixer::mixer().loadSample( FN_SOUND_SHOT_SECONDARY );
}
Enemy::~Enemy() {
delete boundingBox;
}
bool Enemy::isExpired() {
return ( hitpoints <= 0 ||
pos.getY() < -500 ||
pos.getY() > SCREEN_HEIGHT + 500 ||
pos.getX() < -500 ||
pos.getX() > SCREEN_WIDTH + 500 );
}
void Enemy::expire() {
hitpoints = -1;
}
Circle Enemy::getBoundingCircle() {
return Circle( pos, min(spriteEnemy->w / 2, spriteEnemy->h / 2) );
}
BoundingBox *Enemy::getBoundingBox() {
return boundingBox;
}
bool Enemy::collidesWith( const Vector2D &shotPosOld, const Vector2D &shotPosNew ) {
return boundingBox->overlaps(shotPosOld, shotPosNew);
}
bool Enemy::collidesWith( BoundingBox *box ) {
return boundingBox->overlaps( box );
}
bool Enemy::collidesWith( const Circle &circle ) {
return boundingBox->overlaps( circle );
}
bool Enemy::collidesWithAsCircle( const Circle &circle ) {
return ( circle.getRadius() + spriteEnemy->w / 2 > circle.getCenter().distanceTo( pos ) );
}
bool Enemy::collidesWithAsCircle( BoundingBox *box ) {
return ( box->overlaps( Circle( pos, min( spriteEnemy->h / 2, spriteEnemy->w / 2 ) ) ) );
}
void Enemy::update( int dT ) {
move( dT );
if ( !fireByFormation ) {
shootPrimary( dT );
shootSecondary( dT );
}
}
void Enemy::move( int dT ) {
switch ( enemyType ) {
case FIGHTER:
case BOMBER:
{
if ( scrollingOn ) pos += vel * dT / 1000.0;
else pos += (vel - SCROLL_SPEED) * dT / 1000.0;
if ( isInFormation && relTargetPos != Vector2D(0,0) ) {
Vector2D addMovement =
Vector2D( 40, relTargetPos.getDirection(), POLAR ) * dT / 1000.0;
if ( addMovement.getLength() > relTargetPos.getLength() ) {
addMovement.setLength( relTargetPos.getLength() );
}
pos += addMovement;
relTargetPos -= addMovement;
}
updateBoundingBox();
break;
}
case TANK: {
if ( scrollingOn ) pos += vel * dT / 1000.0;
updateBoundingBox();
break;
}
case BOSS_1_MAIN_GUN:
case BOSS_1_ROCKET_LAUNCHER:
case BOSS_1_SHOT_BATTERY_LEFT:
case BOSS_1_SHOT_BATTERY_RIGHT: {
if ( scrollingOn ) {
pos += Vector2D( 0, SCROLL_SPEED * dT / 1000.0 );
updateBoundingBox();
if ( pos.getY() >= BOSS_1_END_Y ) {
scrollingOn = false;
pos.setY( BOSS_1_END_Y );
}
}
break;
}
case BOSS_2: {
if ( boss2PointReached ) {
boss2TargetPos = Vector2D( (rand() % (SCREEN_WIDTH - spriteEnemy->w)) + spriteEnemy->w / 2, rand() % 100 + spriteEnemy->h );
boss2PointReached = false;
} else {
pos += (boss2TargetPos - pos) / 50;
}
if ( pos.distanceTo(boss2TargetPos) < 15.0 ) boss2PointReached = true;
updateBoundingBox();
break;
}
default: cout << "enemys.cc::move(): unknown enemyType" << endl; break;
}
}
void Enemy::updateBoundingBox() {
boundingBox->moveUpperBound( lroundf(pos.getY() - spriteEnemy->h * 0.45) );
boundingBox->moveLeftBound( lroundf(pos.getX() - spriteEnemy->w * 0.45) );
}
void Enemy::firePrimary() {
switch (enemyType) {
case FIGHTER:
{
Shot *shot =
new Shot( ENEMY_SHOT_NORMAL, 666,
pos + Vector2D( 0, spriteEnemy->h / 2 ),
90 );
shots->addShot( shot );
Mixer::mixer().playSample( sndShotPrimary, 0 );
break;
}
case BOMBER:
{
Shot *shot =
new Shot( ENEMY_SHOT_NORMAL, 666,
pos + Vector2D( -7, spriteEnemy->h / 2 ),
100 );
shots->addShot( shot );
shot =
new Shot( ENEMY_SHOT_NORMAL, 666,
pos + Vector2D( +7, spriteEnemy->h / 2 ),
80 );
shots->addShot( shot );
Mixer::mixer().playSample( sndShotPrimary, 0 );
break;
}
case TANK:
{
Shot *shot =
new Shot( ENEMY_SHOT_NORMAL, 666,
pos,
(rand() % 360) - 180 );
shots->addShot( shot );
Mixer::mixer().playSample( sndShotPrimary, 0 );
break;
}
case BOSS_1_MAIN_GUN:
{
Shot *shot =
new Shot( ENEMY_SHOT_NORMAL, 666,
pos,
(rand() % 20) + 80 );
shots->addShot( shot );
Mixer::mixer().playSample( sndShotPrimary, 0 );
break;
}
case BOSS_1_SHOT_BATTERY_LEFT:
{
Shot *shot =
new Shot( ENEMY_SHOT_NORMAL, 666,
pos,
(rand() % 120) + 30 );
shots->addShot( shot );
Mixer::mixer().playSample( sndShotPrimary, 0 );
break;
}
case BOSS_1_SHOT_BATTERY_RIGHT:
{
Shot *shot =
new Shot( ENEMY_SHOT_NORMAL, 666,
pos,
(rand() % 120) + 30 );
shots->addShot( shot );
Mixer::mixer().playSample( sndShotPrimary, 0 );
break;
}
case BOSS_1_ROCKET_LAUNCHER:
{
unsigned int racerIdx = rand() % racers->getNrRacers();
float angle = (racers->getRacer( racerIdx )->getPos() - pos).getDirection();
Shot *shot =
new Shot( ENEMY_SHOT_TANK_ROCKET, 666, pos, angle );
shots->addShot( shot );
Mixer::mixer().playSample( sndShotSecondary, 0 );
break;
}
case BOSS_2:
{
unsigned int racerIdx = rand() % racers->getNrRacers();
float angle = (racers->getRacer( racerIdx )->getPos() - pos).getDirection();
Shot *shot = new Shot( ENEMY_SHOT_NORMAL, 666, pos, angle );
shots->addShot( shot );
Mixer::mixer().playSample( sndShotPrimary, 0 );
break;
}
default:
{
break;
}
}
}
void Enemy::shootPrimary( int dT ) {
nextShotPrimary -= dT;
if ( nextShotPrimary < 0 ) {
firePrimary();
nextShotPrimary =
(rand() % (ENEMY_RAND_WAIT_PRIMARY[ enemyType ]+1)) + ENEMY_COOLDOWN_PRIMARY[ enemyType ];
}
}
void Enemy::fireSecondary() {
switch (enemyType) {
case TANK:
{
unsigned int racerIdx = rand() % racers->getNrRacers();
float angle = (racers->getRacer( racerIdx )->getPos() - pos).getDirection();
Shot *shot =
new Shot( ENEMY_SHOT_TANK_ROCKET, 666, pos, angle );
shots->addShot( shot );
Mixer::mixer().playSample( sndShotSecondary, 0 );
break;
}
case BOSS_2:
{
unsigned int racerIdx = rand() % racers->getNrRacers();
float angle = (racers->getRacer( racerIdx )->getPos() - pos).getDirection();
Shot *shot = new Shot( ENEMY_SHOT_TANK_ROCKET, 666, pos - Vector2D(-80,0), angle );
shots->addShot( shot );
shot = new Shot( ENEMY_SHOT_TANK_ROCKET, 666, pos - Vector2D(+80,0), angle );
shots->addShot( shot );
Mixer::mixer().playSample( sndShotSecondary, 0 );
break;
}
default:
{
break;
}
}
}
void Enemy::shootSecondary( int dT ) {
nextShotSecondary -= dT;
if ( nextShotSecondary < 0 ) {
fireSecondary();
nextShotSecondary =
(rand() % (ENEMY_RAND_WAIT_SECONDARY[ enemyType ]+1)) +
ENEMY_COOLDOWN_SECONDARY[ enemyType ];
}
}
void Enemy::doDamage( ShotTypes shotType, int fromWhichPlayer ) {
bool allreadyDead = isExpired();
switch (shotType) {
case SHOT_NORMAL: hitpoints -= DAMAGE_SHOT_NORMAL; break;
case SHOT_NORMAL_HEAVY: hitpoints -= DAMAGE_SHOT_NORMAL_HEAVY; break;
case SHOT_DOUBLE: hitpoints -= DAMAGE_SHOT_DOUBLE; break;
case SHOT_DOUBLE_HEAVY: hitpoints -= DAMAGE_SHOT_DOUBLE_HEAVY; break;
case SHOT_TRIPLE: hitpoints -= DAMAGE_SHOT_TRIPLE; break;
case SHOT_HF_NORMAL: hitpoints -= DAMAGE_SHOT_HF_NORMAL; break;
case SHOT_HF_DOUBLE: hitpoints -= DAMAGE_SHOT_HF_DOUBLE; break;
case SHOT_HF_TRIPLE: hitpoints -= DAMAGE_SHOT_HF_TRIPLE; break;
case SHOT_HF_QUATTRO: hitpoints -= DAMAGE_SHOT_HF_QUATTRO; break;
case SHOT_HF_QUINTO: hitpoints -= DAMAGE_SHOT_HF_QUINTO; break;
case SHOT_DUMBFIRE: hitpoints -= DAMAGE_SHOT_DUMBFIRE; break;
case SHOT_DUMBFIRE_DOUBLE: hitpoints -= DAMAGE_SHOT_DUMBFIRE_DOUBLE; break;
case SHOT_KICK_ASS_ROCKET: hitpoints -= DAMAGE_SHOT_KICK_ASS_ROCKET; break;
case SHOT_HELLFIRE: hitpoints -= DAMAGE_SHOT_HELLFIRE; break;
case SHOT_MACHINE_GUN: hitpoints -= DAMAGE_SHOT_MACHINE_GUN; break;
case SHOT_ENERGY_BEAM: hitpoints -= DAMAGE_SHOT_ENERGY_BEAM; break;
case SHOT_HF_DUMBFIRE: hitpoints -= DAMAGE_SHOT_HF_DUMBFIRE; break;
case SHOT_HF_DUMBFIRE_DOUBLE: hitpoints -= DAMAGE_SHOT_HF_DUMBFIRE_DOUBLE; break;
case SHOT_HF_KICK_ASS_ROCKET: hitpoints -= DAMAGE_SHOT_HF_KICK_ASS_ROCKET; break;
case SHOT_HF_LASER: hitpoints -= DAMAGE_SHOT_HF_LASER; break;
case SPECIAL_SHOT_HEATSEEKER: hitpoints -= DAMAGE_SPECIAL_SHOT_HEATSEEKER; break;
case SPECIAL_SHOT_NUKE: hitpoints -= DAMAGE_SPECIAL_SHOT_NUKE; break;
default:
{
cout << "Enemy::doDamage: unexpected shotType: " << shotType << endl;
break;
}
}
if ( enemyType < NR_ENEMY_TYPES_NORMAL ) {
// the enemy just died -> explode, generate item,
if ( (!allreadyDead) && isExpired() ) {
// the player gets points, who did the final shot
if ( 0 <= fromWhichPlayer && fromWhichPlayer < (int)racers->getNrRacers() ) {
racers->getRacer( fromWhichPlayer )->receivePoints( ENEMY_POINTS_FOR_DEST[ enemyType ]);
}
// explode
Explosion *newExplosion =
new Explosion( FN_EXPLOSION_ENEMY, pos, vel, EXPLOSION_NORMAL_AIR );
explosions->addExplosion( newExplosion );
// generate wreck
Wreck *newWreck = new Wreck( pos, WRECK_FOR_ENEMYTYPE[ enemyType ]);
wrecks->addWreck( newWreck );
// generate item
if ( rand() % ENEMY_DIES_ITEM_APPEAR_CHANCE[ enemyType ] == 0 ) {
items->generateItemNow( pos, vel / 10.0 );
}
}
}
// it's a boss!!!
else {
if ( (!allreadyDead) && isExpired() ) {
// explode
Explosion *newExplosion;
for(int i = 0; i < 10; i++){
newExplosion = new Explosion(
FN_EXPLOSION_ENEMY,
pos + Vector2D(rand() % spriteEnemy->w - spriteEnemy->w / 2, rand() % spriteEnemy->h - spriteEnemy->h / 2),
vel + Vector2D(rand() % 100 - 50, rand() % 100 - 50) / 5.0,
EXPLOSION_NORMAL_GROUND );
explosions->addExplosion( newExplosion );
}
// generate wreck
Wreck *newWreck = new Wreck( pos, WRECK_FOR_ENEMYTYPE[ enemyType ]);
wrecks->addWreck( newWreck );
}
}
}
void Enemy::drawGroundEnemy( SdlCompat_AcceleratedSurface *screen ) {
if ( ENEMY_FLYING[ enemyType ] ) return;
SDL_Rect destR;
destR.x = lroundf(pos.getX()) - spriteEnemy->w / 2;
destR.y = lroundf(pos.getY()) - spriteEnemy->h / 2;
destR.w = spriteEnemy->w;
destR.h = spriteEnemy->h;
SDL_BlitSurface( spriteEnemy, 0, screen, &destR );
}
void Enemy::drawAirEnemy( SdlCompat_AcceleratedSurface *screen ) {
if ( !ENEMY_FLYING[ enemyType ] ) return;
SDL_Rect destR;
destR.x = lroundf(pos.getX()) - spriteEnemy->w / 2;
destR.y = lroundf(pos.getY()) - spriteEnemy->h / 2;
destR.w = spriteEnemy->w;
destR.h = spriteEnemy->h;
SDL_BlitSurface( spriteEnemy, 0, screen, &destR );
}
void Enemy::drawShadow( SdlCompat_AcceleratedSurface *screen ) {
if ( !ENEMY_FLYING[ enemyType ] ) return;
SDL_Rect destR;
destR.x = lroundf(pos.getX()) - spriteShadow->w / 2 - ENEMY_FLYING_HEIGHT[ enemyType ];
destR.y = lroundf(pos.getY()) - spriteShadow->h / 2 + ENEMY_FLYING_HEIGHT[ enemyType ];
destR.w = spriteShadow->w;
destR.h = spriteShadow->h;
SDL_BlitSurface( spriteShadow, 0, screen, &destR );
}
void Enemy::drawStats( SdlCompat_AcceleratedSurface *screen ) {
// draw status of the bosses
float pixPerHP = spriteEnemy->w / (float)(ENEMY_HITPOINTS[ enemyType ]);
SDL_Rect destR;
// draw damage
destR.x = lroundf(pos.getX()) - spriteEnemy->w / 2;
destR.y = lroundf(pos.getY()) - spriteEnemy->h / 2 - 4;
float damageToDraw = min( (float)ENEMY_HITPOINTS[ enemyType ] / 2, hitpoints );
destR.w = lroundf(pixPerHP * damageToDraw);
destR.h = 3;
SDL_FillRect( screen, &destR, SDL_MapRGB(screen->format, 255, 0, 0) );
// draw shields
destR.x = lroundf(pos.getX());
destR.y = lroundf(pos.getY()) - spriteEnemy->h / 2 - 4;
float shieldToDraw = min( (float)ENEMY_HITPOINTS[ enemyType ] / 2,
hitpoints - ENEMY_HITPOINTS[ enemyType ] / 2 );
if ( shieldToDraw < 1 ) destR.w = 0;
else destR.w = lroundf(pixPerHP * shieldToDraw);
destR.h = 3;
SDL_FillRect(screen, &destR, SDL_MapRGB(screen->format, 0, 255, 0) );
}