1 /***************************************************************************
2   alienBlaster
3   Copyright (C) 2004
4   Paul Grathwohl, Arne Hormann, Daniel Kuehn, Soenke Schwardt
5 
6   This program is free software; you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by
8   the Free Software Foundation; either version 2, or (at your option)
9   any later version.
10 
11   This program is distributed in the hope that it will be useful,
12   but WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   GNU General Public License for more details.
15 
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.
19 ***************************************************************************/
20 #include "enemy.h"
21 #include "SDL.h"
22 #include "surfaceDB.h"
23 #include "boundingBox.h"
24 #include "mixer.h"
25 #include "shots.h"
26 #include "shot.h"
27 #include "racers.h"
28 #include "racer.h"
29 #include "items.h"
30 #include "item.h"
31 #include "wrecks.h"
32 #include "wreck.h"
33 #include "global.h"
34 #include "explosions.h"
35 #include "explosion.h"
36 #include "options.h"
37 
Enemy(Vector2D pos,Vector2D vel,EnemyTypes whichEnemyType,bool isInFormation,bool fireByFormation)38 Enemy::Enemy( Vector2D pos, Vector2D vel, EnemyTypes whichEnemyType,
39 	      bool isInFormation, bool fireByFormation ) {
40 
41   this->isInFormation = isInFormation;
42   this->fireByFormation = fireByFormation;
43   enemyType = whichEnemyType;
44   hitpoints = ENEMY_HITPOINTS[ enemyType ];
45 
46   this->pos = pos;
47   this->vel = vel;
48   relTargetPos = Vector2D(0,0);
49 
50   switch ( enemyType ) {
51   case FIGHTER:
52     {
53       string fn = LVL_ENEMY_FIGHTER;
54       levelConf->getStr( LVL_ENEMY_FIGHTER, fn );
55       spriteEnemy = surfaceDB.loadSurface( fn );
56 
57       fn = LVL_ENEMY_FIGHTER_SHADOW;
58       levelConf->getStr( LVL_ENEMY_FIGHTER_SHADOW, fn );
59       spriteShadow = surfaceDB.loadSurface( fn, true );
60       break;
61     }
62   case BOMBER:
63     {
64       string fn = LVL_ENEMY_BOMBER;
65       levelConf->getStr( LVL_ENEMY_BOMBER, fn );
66       spriteEnemy = surfaceDB.loadSurface( fn );
67 
68       fn = LVL_ENEMY_BOMBER_SHADOW;
69       levelConf->getStr( LVL_ENEMY_BOMBER_SHADOW, fn );
70       spriteShadow = surfaceDB.loadSurface( fn, true );
71       break;
72     }
73   case TANK:
74     {
75       string fn = LVL_ENEMY_TANK;
76       levelConf->getStr( LVL_ENEMY_TANK, fn );
77       spriteEnemy = surfaceDB.loadSurface( fn );
78       break;
79     }
80 
81   case BOSS_1_MAIN_GUN:
82     {
83       string fn = LVL_ENEMY_BOSS_1_MAIN_GUN;
84       levelConf->getStr( LVL_ENEMY_BOSS_1_MAIN_GUN, fn );
85       spriteEnemy = surfaceDB.loadSurface( fn );
86       break;
87     }
88   case BOSS_1_ROCKET_LAUNCHER:
89     {
90       string fn = LVL_ENEMY_BOSS_1_ROCKET_LAUNCHER;
91       levelConf->getStr( LVL_ENEMY_BOSS_1_ROCKET_LAUNCHER, fn );
92       spriteEnemy = surfaceDB.loadSurface( fn );
93       break;
94     }
95   case BOSS_1_SHOT_BATTERY_LEFT:
96     {
97       string fn = LVL_ENEMY_BOSS_1_SHOT_BATTERY_LEFT;
98       levelConf->getStr( LVL_ENEMY_BOSS_1_SHOT_BATTERY_LEFT, fn );
99       spriteEnemy = surfaceDB.loadSurface( fn );
100       break;
101     }
102   case BOSS_1_SHOT_BATTERY_RIGHT:
103     {
104       string fn = LVL_ENEMY_BOSS_1_SHOT_BATTERY_RIGHT;
105       levelConf->getStr( LVL_ENEMY_BOSS_1_SHOT_BATTERY_RIGHT, fn );
106       spriteEnemy = surfaceDB.loadSurface( fn );
107       break;
108     }
109   case BOSS_2:
110     {
111       spriteEnemy = surfaceDB.loadSurface( FN_ENEMY_BOSS_2 );
112       spriteShadow = surfaceDB.loadSurface( FN_ENEMY_BOSS_2_SHADOW, true );
113       boss2PointReached = false;
114       boss2TargetPos = Vector2D(SCREEN_WIDTH / 2, 100);
115       break;
116     }
117 
118   default:
119     {
120       string fn = LVL_ENEMY_FIGHTER;
121       levelConf->getStr( LVL_ENEMY_FIGHTER, fn );
122       spriteEnemy = surfaceDB.loadSurface( fn );
123       break;
124     }
125   }
126 
127   boundingBox = new BoundingBox( lroundf(pos.getX() - spriteEnemy->w * 0.45),
128 				 lroundf(pos.getY() - spriteEnemy->h * 0.45),
129 				 lroundf(spriteEnemy->w * 0.9),
130 				 lroundf(spriteEnemy->h * 0.9) );
131   nextShotPrimary = rand() % (ENEMY_RAND_WAIT_PRIMARY[ enemyType ]+1);
132   nextShotSecondary = rand() % (ENEMY_RAND_WAIT_SECONDARY[ enemyType ]+1);
133 
134   sndShotPrimary = mixer.loadSample( FN_SOUND_SHOT_PRIMARY );
135   sndShotSecondary = mixer.loadSample( FN_SOUND_SHOT_SECONDARY );
136 }
137 
~Enemy()138 Enemy::~Enemy() {
139   delete boundingBox;
140 }
141 
142 
isExpired()143 bool Enemy::isExpired() {
144   return ( hitpoints <= 0 ||
145 	   pos.getY() < -500 ||
146 	   pos.getY() > SCREEN_HEIGHT + 500 ||
147 	   pos.getX() < -500 ||
148 	   pos.getX() > SCREEN_WIDTH + 500 );
149 }
150 
151 
expire()152 void Enemy::expire() {
153   hitpoints = -1;
154 }
155 
getBoundingCircle()156 Circle Enemy::getBoundingCircle() {
157   return Circle( pos, min(spriteEnemy->w / 2, spriteEnemy->h / 2) );
158 }
159 
getBoundingBox()160 BoundingBox *Enemy::getBoundingBox() {
161   return boundingBox;
162 }
163 
collidesWith(const Vector2D & shotPosOld,const Vector2D & shotPosNew)164 bool Enemy::collidesWith( const Vector2D &shotPosOld, const Vector2D &shotPosNew ) {
165   return boundingBox->overlaps(shotPosOld, shotPosNew);
166 }
167 
collidesWith(BoundingBox * box)168 bool Enemy::collidesWith( BoundingBox *box ) {
169   return boundingBox->overlaps( box );
170 }
171 
collidesWith(const Circle & circle)172 bool Enemy::collidesWith( const Circle &circle ) {
173   return boundingBox->overlaps( circle );
174 }
175 
collidesWithAsCircle(const Circle & circle)176 bool Enemy::collidesWithAsCircle( const Circle &circle ) {
177   return ( circle.getRadius() + spriteEnemy->w / 2 > circle.getCenter().distanceTo( pos ) );
178 }
179 
collidesWithAsCircle(BoundingBox * box)180 bool Enemy::collidesWithAsCircle( BoundingBox *box ) {
181   return ( box->overlaps( Circle( pos, min( spriteEnemy->h / 2, spriteEnemy->w / 2 ) ) ) );
182 }
183 
184 
update(int dT)185 void Enemy::update( int dT ) {
186   move( dT );
187   if ( !fireByFormation ) {
188     shootPrimary( dT );
189     shootSecondary( dT );
190   }
191 }
192 
move(int dT)193 void Enemy::move( int dT ) {
194   switch ( enemyType ) {
195   case FIGHTER:
196   case BOMBER:
197     {
198       if ( scrollingOn ) pos += vel * dT / 1000.0;
199       else pos += (vel - SCROLL_SPEED) * dT / 1000.0;
200       if ( isInFormation && relTargetPos != Vector2D(0,0) ) {
201         Vector2D addMovement =
202           Vector2D( 40, relTargetPos.getDirection(), POLAR ) * dT / 1000.0;
203         if ( addMovement.getLength() > relTargetPos.getLength() ) {
204           addMovement.setLength( relTargetPos.getLength() );
205         }
206         pos += addMovement;
207         relTargetPos -= addMovement;
208       }
209       updateBoundingBox();
210       break;
211     }
212   case TANK: {
213     if ( scrollingOn ) pos += vel * dT / 1000.0;
214     updateBoundingBox();
215     break;
216   }
217   case BOSS_1_MAIN_GUN:
218   case BOSS_1_ROCKET_LAUNCHER:
219   case BOSS_1_SHOT_BATTERY_LEFT:
220   case BOSS_1_SHOT_BATTERY_RIGHT: {
221     if ( scrollingOn ) {
222       pos += Vector2D( 0, SCROLL_SPEED * dT / 1000.0 );
223       updateBoundingBox();
224       if ( pos.getY() >= BOSS_1_END_Y ) {
225         scrollingOn = false;
226         pos.setY( BOSS_1_END_Y );
227       }
228     }
229     break;
230   }
231   case BOSS_2: {
232     if ( boss2PointReached ) {
233       boss2TargetPos = Vector2D( (rand() % (SCREEN_WIDTH - spriteEnemy->w)) + spriteEnemy->w / 2, rand() % 100 + spriteEnemy->h );
234       boss2PointReached = false;
235     } else {
236       pos += (boss2TargetPos - pos) / 50;
237     }
238     if ( pos.distanceTo(boss2TargetPos) < 15.0 ) boss2PointReached = true;
239     updateBoundingBox();
240     break;
241   }
242   default: cout << "enemys.cc::move(): unknown enemyType" << endl; break;
243   }
244 }
245 
updateBoundingBox()246 void Enemy::updateBoundingBox() {
247   boundingBox->moveUpperBound( lroundf(pos.getY() - spriteEnemy->h * 0.45) );
248   boundingBox->moveLeftBound( lroundf(pos.getX() - spriteEnemy->w * 0.45) );
249 }
250 
251 
firePrimary()252 void Enemy::firePrimary() {
253   switch (enemyType) {
254   case FIGHTER:
255     {
256       Shot *shot =
257 	new Shot( ENEMY_SHOT_NORMAL, 666,
258 		  pos + Vector2D( 0, spriteEnemy->h / 2 ),
259 		  90 );
260       shots->addShot( shot );
261       mixer.playSample( sndShotPrimary, 0 );
262       break;
263     }
264   case BOMBER:
265     {
266       Shot *shot =
267 	new Shot( ENEMY_SHOT_NORMAL, 666,
268 		  pos + Vector2D( -7, spriteEnemy->h / 2 ),
269 		  100 );
270       shots->addShot( shot );
271       shot =
272 	new Shot( ENEMY_SHOT_NORMAL, 666,
273 		  pos + Vector2D( +7, spriteEnemy->h / 2 ),
274 		  80 );
275       shots->addShot( shot );
276       mixer.playSample( sndShotPrimary, 0 );
277       break;
278     }
279   case TANK:
280     {
281       Shot *shot =
282 	new Shot( ENEMY_SHOT_NORMAL, 666,
283 		  pos,
284 		  (rand() % 360) - 180 );
285       shots->addShot( shot );
286       mixer.playSample( sndShotPrimary, 0 );
287       break;
288     }
289   case BOSS_1_MAIN_GUN:
290     {
291       Shot *shot =
292 	new Shot( ENEMY_SHOT_NORMAL, 666,
293 		  pos,
294 		  (rand() % 20) + 80 );
295       shots->addShot( shot );
296       mixer.playSample( sndShotPrimary, 0 );
297       break;
298     }
299   case BOSS_1_SHOT_BATTERY_LEFT:
300     {
301       Shot *shot =
302 	new Shot( ENEMY_SHOT_NORMAL, 666,
303 		  pos,
304 		  (rand() % 120) + 30 );
305       shots->addShot( shot );
306       mixer.playSample( sndShotPrimary, 0 );
307       break;
308     }
309   case BOSS_1_SHOT_BATTERY_RIGHT:
310     {
311       Shot *shot =
312 	new Shot( ENEMY_SHOT_NORMAL, 666,
313 		  pos,
314 		  (rand() % 120) + 30 );
315       shots->addShot( shot );
316       mixer.playSample( sndShotPrimary, 0 );
317       break;
318     }
319   case BOSS_1_ROCKET_LAUNCHER:
320     {
321       unsigned int racerIdx = rand() % racers->getNrRacers();
322       float angle = (racers->getRacer( racerIdx )->getPos() - pos).getDirection();
323       Shot *shot =
324 	new Shot( ENEMY_SHOT_TANK_ROCKET, 666, pos, angle );
325       shots->addShot( shot );
326       mixer.playSample( sndShotSecondary, 0 );
327       break;
328     }
329   case BOSS_2:
330     {
331       unsigned int racerIdx = rand() % racers->getNrRacers();
332       float angle = (racers->getRacer( racerIdx )->getPos() - pos).getDirection();
333       Shot *shot = new Shot( ENEMY_SHOT_NORMAL, 666, pos, angle );
334       shots->addShot( shot );
335       mixer.playSample( sndShotPrimary, 0 );
336       break;
337     }
338   default:
339     {
340       break;
341     }
342   }
343 }
344 
345 
shootPrimary(int dT)346 void Enemy::shootPrimary( int dT ) {
347   nextShotPrimary -= dT;
348   if ( nextShotPrimary < 0 ) {
349     firePrimary();
350     nextShotPrimary =
351       (rand() % (ENEMY_RAND_WAIT_PRIMARY[ enemyType ]+1)) + ENEMY_COOLDOWN_PRIMARY[ enemyType ];
352   }
353 }
354 
355 
fireSecondary()356 void Enemy::fireSecondary() {
357   switch (enemyType) {
358   case TANK:
359     {
360       unsigned int racerIdx = rand() % racers->getNrRacers();
361       float angle = (racers->getRacer( racerIdx )->getPos() - pos).getDirection();
362 
363       Shot *shot =
364 	new Shot( ENEMY_SHOT_TANK_ROCKET, 666, pos, angle );
365       shots->addShot( shot );
366       mixer.playSample( sndShotSecondary, 0 );
367       break;
368     }
369   case BOSS_2:
370     {
371       unsigned int racerIdx = rand() % racers->getNrRacers();
372       float angle = (racers->getRacer( racerIdx )->getPos() - pos).getDirection();
373       Shot *shot = new Shot( ENEMY_SHOT_TANK_ROCKET, 666, pos - Vector2D(-80,0), angle );
374       shots->addShot( shot );
375       shot = new Shot( ENEMY_SHOT_TANK_ROCKET, 666, pos - Vector2D(+80,0), angle );
376       shots->addShot( shot );
377       mixer.playSample( sndShotSecondary, 0 );
378       break;
379     }
380   default:
381     {
382       break;
383     }
384   }
385 }
386 
shootSecondary(int dT)387 void Enemy::shootSecondary( int dT ) {
388   nextShotSecondary -= dT;
389   if ( nextShotSecondary < 0 ) {
390     fireSecondary();
391     nextShotSecondary =
392       (rand() % (ENEMY_RAND_WAIT_SECONDARY[ enemyType ]+1)) +
393       ENEMY_COOLDOWN_SECONDARY[ enemyType ];
394   }
395 }
396 
397 
doDamage(ShotTypes shotType,int fromWhichPlayer)398 void Enemy::doDamage( ShotTypes shotType, int fromWhichPlayer ) {
399   bool allreadyDead = isExpired();
400 
401   switch (shotType) {
402   case SHOT_NORMAL: hitpoints -= DAMAGE_SHOT_NORMAL; break;
403   case SHOT_NORMAL_HEAVY: hitpoints -= DAMAGE_SHOT_NORMAL_HEAVY; break;
404   case SHOT_DOUBLE: hitpoints -= DAMAGE_SHOT_DOUBLE; break;
405   case SHOT_DOUBLE_HEAVY: hitpoints -= DAMAGE_SHOT_DOUBLE_HEAVY; break;
406   case SHOT_TRIPLE: hitpoints -= DAMAGE_SHOT_TRIPLE; break;
407 
408   case SHOT_HF_NORMAL: hitpoints -= DAMAGE_SHOT_HF_NORMAL; break;
409   case SHOT_HF_DOUBLE: hitpoints -= DAMAGE_SHOT_HF_DOUBLE; break;
410   case SHOT_HF_TRIPLE: hitpoints -= DAMAGE_SHOT_HF_TRIPLE; break;
411   case SHOT_HF_QUATTRO: hitpoints -= DAMAGE_SHOT_HF_QUATTRO; break;
412   case SHOT_HF_QUINTO: hitpoints -= DAMAGE_SHOT_HF_QUINTO; break;
413 
414   case SHOT_DUMBFIRE: hitpoints -= DAMAGE_SHOT_DUMBFIRE; break;
415   case SHOT_DUMBFIRE_DOUBLE: hitpoints -= DAMAGE_SHOT_DUMBFIRE_DOUBLE; break;
416   case SHOT_KICK_ASS_ROCKET: hitpoints -= DAMAGE_SHOT_KICK_ASS_ROCKET; break;
417   case SHOT_HELLFIRE: hitpoints -= DAMAGE_SHOT_HELLFIRE; break;
418   case SHOT_MACHINE_GUN: hitpoints -= DAMAGE_SHOT_MACHINE_GUN; break;
419   case SHOT_ENERGY_BEAM: hitpoints -= DAMAGE_SHOT_ENERGY_BEAM; break;
420 
421   case SHOT_HF_DUMBFIRE: hitpoints -= DAMAGE_SHOT_HF_DUMBFIRE; break;
422   case SHOT_HF_DUMBFIRE_DOUBLE: hitpoints -= DAMAGE_SHOT_HF_DUMBFIRE_DOUBLE; break;
423   case SHOT_HF_KICK_ASS_ROCKET: hitpoints -= DAMAGE_SHOT_HF_KICK_ASS_ROCKET; break;
424   case SHOT_HF_LASER: hitpoints -= DAMAGE_SHOT_HF_LASER; break;
425 
426   case SPECIAL_SHOT_HEATSEEKER: hitpoints -= DAMAGE_SPECIAL_SHOT_HEATSEEKER; break;
427   case SPECIAL_SHOT_NUKE: hitpoints -= DAMAGE_SPECIAL_SHOT_NUKE; break;
428   default:
429     {
430       cout << "Enemy::doDamage: unexpected shotType: " << shotType << endl;
431       break;
432     }
433   }
434 
435   if ( enemyType < NR_ENEMY_TYPES_NORMAL ) {
436     // the enemy just died -> explode, generate item,
437     if ( (!allreadyDead) && isExpired() ) {
438       // the player gets points, who did the final shot
439       if ( 0 <= fromWhichPlayer && fromWhichPlayer < (int)racers->getNrRacers() ) {
440         racers->getRacer( fromWhichPlayer )->receivePoints( ENEMY_POINTS_FOR_DEST[ enemyType ]);
441       }
442       // explode
443       Explosion *newExplosion =
444         new Explosion( FN_EXPLOSION_ENEMY, pos, vel, EXPLOSION_NORMAL_AIR );
445       explosions->addExplosion( newExplosion );
446       // generate wreck
447       Wreck *newWreck = new Wreck( pos, WRECK_FOR_ENEMYTYPE[ enemyType ]);
448       wrecks->addWreck( newWreck );
449       // generate item
450       if ( rand() % ENEMY_DIES_ITEM_APPEAR_CHANCE[ enemyType ] == 0 ) {
451         items->generateItemNow( pos, vel / 10.0 );
452       }
453     }
454   }
455   // it's a boss!!!
456   else {
457     if ( (!allreadyDead) && isExpired() ) {
458       // explode
459       Explosion *newExplosion;
460       for(int i = 0; i < 10; i++){
461         newExplosion = new Explosion(
462           FN_EXPLOSION_ENEMY,
463           pos + Vector2D(rand() % spriteEnemy->w - spriteEnemy->w / 2, rand() % spriteEnemy->h - spriteEnemy->h / 2),
464           vel + Vector2D(rand() % 100 - 50, rand() % 100 - 50) / 5.0,
465           EXPLOSION_NORMAL_GROUND );
466         explosions->addExplosion( newExplosion );
467       }
468 
469       // generate wreck
470       Wreck *newWreck = new Wreck( pos, WRECK_FOR_ENEMYTYPE[ enemyType ]);
471       wrecks->addWreck( newWreck );
472     }
473   }
474 }
475 
476 
drawGroundEnemy(SDL_Surface * screen)477 void Enemy::drawGroundEnemy( SDL_Surface *screen ) {
478   if ( ENEMY_FLYING[ enemyType ] ) return;
479 
480   SDL_Rect destR;
481 
482   destR.x = lroundf(pos.getX()) - spriteEnemy->w / 2;
483   destR.y = lroundf(pos.getY()) - spriteEnemy->h / 2;
484   destR.w = spriteEnemy->w;
485   destR.h = spriteEnemy->h;
486 
487   SDL_BlitSurface( spriteEnemy, 0, screen, &destR );
488 }
489 
drawAirEnemy(SDL_Surface * screen)490 void Enemy::drawAirEnemy( SDL_Surface *screen ) {
491   if ( !ENEMY_FLYING[ enemyType ] ) return;
492 
493   SDL_Rect destR;
494 
495   destR.x = lroundf(pos.getX()) - spriteEnemy->w / 2;
496   destR.y = lroundf(pos.getY()) - spriteEnemy->h / 2;
497   destR.w = spriteEnemy->w;
498   destR.h = spriteEnemy->h;
499 
500   SDL_BlitSurface( spriteEnemy, 0, screen, &destR );
501 }
502 
drawShadow(SDL_Surface * screen)503 void Enemy::drawShadow( SDL_Surface *screen ) {
504   if ( !ENEMY_FLYING[ enemyType ] ) return;
505 
506   SDL_Rect destR;
507 
508   destR.x = lroundf(pos.getX()) - spriteShadow->w / 2 - ENEMY_FLYING_HEIGHT[ enemyType ];
509   destR.y = lroundf(pos.getY()) - spriteShadow->h / 2 + ENEMY_FLYING_HEIGHT[ enemyType ];
510   destR.w = spriteShadow->w;
511   destR.h = spriteShadow->h;
512 
513   SDL_BlitSurface( spriteShadow, 0, screen, &destR );
514 }
515 
516 
drawStats(SDL_Surface * screen)517 void Enemy::drawStats( SDL_Surface *screen ) {
518   // draw status of the bosses
519   float pixPerHP = spriteEnemy->w / (float)(ENEMY_HITPOINTS[ enemyType ]);
520   SDL_Rect destR;
521   // draw damage
522   destR.x = lroundf(pos.getX()) - spriteEnemy->w / 2;
523   destR.y = lroundf(pos.getY()) - spriteEnemy->h / 2 - 4;
524   float damageToDraw = min( (float)ENEMY_HITPOINTS[ enemyType ] / 2, hitpoints );
525   destR.w = lroundf(pixPerHP * damageToDraw);
526   destR.h = 3;
527   SDL_FillRect( screen, &destR, SDL_MapRGB(screen->format, 255, 0, 0) );
528   // draw shields
529   destR.x = lroundf(pos.getX());
530   destR.y = lroundf(pos.getY()) - spriteEnemy->h / 2 - 4;
531   float shieldToDraw = min( (float)ENEMY_HITPOINTS[ enemyType ] / 2,
532 			    hitpoints - ENEMY_HITPOINTS[ enemyType ] / 2 );
533   if ( shieldToDraw < 1 ) destR.w = 0;
534   else destR.w = lroundf(pixPerHP * shieldToDraw);
535   destR.h = 3;
536   SDL_FillRect(screen, &destR, SDL_MapRGB(screen->format, 0, 255, 0) );
537 }
538