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