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 using namespace std;
21 
22 #include <iostream>
23 #include "shot.h"
24 #include "surfaceDB.h"
25 #include "racers.h"
26 #include "racer.h"
27 #include "explosions.h"
28 #include "explosion.h"
29 #include "enemys.h"
30 #include "enemy.h"
31 #include "smokePuffs.h"
32 #include "boundingBox.h"
33 
Shot(ShotTypes shotType,int playerNr,Vector2D position,float angle)34 Shot::Shot( ShotTypes shotType, int playerNr, Vector2D position, float angle ) {
35 
36   this->shotType = shotType;
37   fromWhichPlayer = playerNr;
38 
39   pos = position;
40   collidedWithGround = false;
41   deflectedBySonicFromPlayer1 = false;
42   deflectedBySonicFromPlayer2 = false;
43 
44   generatesSmokePuffs = false;
45   timeToNextSmokePuff = 100;
46 
47   switch (shotType) {
48     // primary shots
49   case SHOT_NORMAL:
50     {
51       vel = Vector2D( VEL_SHOT_NORMAL, angle, POLAR);
52       timeToLive = LIFETIME_SHOT_NORMAL;
53       sprite = surfaceDB.loadSurface( FN_SHOT_NORMAL );
54       break;
55     }
56   case SHOT_NORMAL_HEAVY:
57     {
58       vel = Vector2D( VEL_SHOT_NORMAL_HEAVY, angle, POLAR);
59       timeToLive = LIFETIME_SHOT_NORMAL_HEAVY;
60       sprite = surfaceDB.loadSurface( FN_SHOT_NORMAL_HEAVY );
61       break;
62     }
63   case SHOT_DOUBLE:
64     {
65       vel = Vector2D(VEL_SHOT_DOUBLE, angle, POLAR);
66       timeToLive = LIFETIME_SHOT_DOUBLE;
67       sprite = surfaceDB.loadSurface( FN_SHOT_DOUBLE );
68       break;
69     }
70   case SHOT_DOUBLE_HEAVY:
71     {
72       vel = Vector2D(VEL_SHOT_DOUBLE_HEAVY, angle, POLAR);
73       timeToLive = LIFETIME_SHOT_DOUBLE_HEAVY;
74       sprite = surfaceDB.loadSurface( FN_SHOT_DOUBLE_HEAVY );
75       break;
76     }
77   case SHOT_TRIPLE:
78     {
79       vel = Vector2D(VEL_SHOT_TRIPLE, angle, POLAR);
80       timeToLive = LIFETIME_SHOT_TRIPLE;
81       sprite = surfaceDB.loadSurface( FN_SHOT_TRIPLE );
82       break;
83     }
84 
85     // primary shots heavy fighter
86   case SHOT_HF_NORMAL:
87     {
88       vel = Vector2D( VEL_SHOT_HF_NORMAL, angle, POLAR);
89       timeToLive = LIFETIME_SHOT_HF_NORMAL;
90       sprite = surfaceDB.loadSurface( FN_SHOT_HF_NORMAL );
91       break;
92     }
93   case SHOT_HF_DOUBLE:
94     {
95       vel = Vector2D( VEL_SHOT_HF_DOUBLE, angle, POLAR);
96       timeToLive = LIFETIME_SHOT_HF_DOUBLE;
97       sprite = surfaceDB.loadSurface( FN_SHOT_HF_DOUBLE );
98       break;
99     }
100   case SHOT_HF_TRIPLE:
101     {
102       vel = Vector2D(VEL_SHOT_HF_TRIPLE, angle, POLAR);
103       timeToLive = LIFETIME_SHOT_HF_TRIPLE;
104       sprite = surfaceDB.loadSurface( FN_SHOT_HF_TRIPLE );
105       break;
106     }
107   case SHOT_HF_QUATTRO:
108     {
109       vel = Vector2D(VEL_SHOT_HF_QUATTRO, angle, POLAR);
110       timeToLive = LIFETIME_SHOT_HF_QUATTRO;
111       sprite = surfaceDB.loadSurface( FN_SHOT_HF_QUATTRO );
112       break;
113     }
114   case SHOT_HF_QUINTO:
115     {
116       vel = Vector2D(VEL_SHOT_HF_QUINTO, angle, POLAR);
117       timeToLive = LIFETIME_SHOT_HF_QUINTO;
118       sprite = surfaceDB.loadSurface( FN_SHOT_HF_QUINTO );
119       break;
120     }
121 
122     // secondary shots
123   case SHOT_DUMBFIRE:
124     {
125       vel = Vector2D(VEL_SHOT_DUMBFIRE, angle, POLAR);
126       timeToLive = LIFETIME_SHOT_DUMBFIRE;
127       sprite = surfaceDB.loadSurface( FN_SHOT_DUMBFIRE );
128       generatesSmokePuffs = true;
129       break;
130     }
131   case SHOT_DUMBFIRE_DOUBLE:
132     {
133       vel = Vector2D(VEL_SHOT_DUMBFIRE_DOUBLE, angle, POLAR);
134       timeToLive = LIFETIME_SHOT_DUMBFIRE_DOUBLE;
135       sprite = surfaceDB.loadSurface( FN_SHOT_DUMBFIRE_DOUBLE );
136       generatesSmokePuffs = true;
137       break;
138     }
139   case SHOT_KICK_ASS_ROCKET:
140     {
141       vel = Vector2D(VEL_SHOT_KICK_ASS_ROCKET, angle, POLAR);
142       timeToLive = LIFETIME_SHOT_KICK_ASS_ROCKET;
143       sprite = surfaceDB.loadSurface( FN_SHOT_KICK_ASS_ROCKET );
144       spriteShadow = surfaceDB.loadSurface( FN_SHOT_KICK_ASS_ROCKET_SHADOW, true );
145       break;
146     }
147   case SHOT_HELLFIRE:
148     {
149       vel = Vector2D(VEL_SHOT_HELLFIRE / 2.0, angle, POLAR);
150       timeToLive = LIFETIME_SHOT_HELLFIRE;
151       sprite = surfaceDB.loadSurface( FN_SHOT_HELLFIRE );
152       spriteShadow = surfaceDB.loadSurface( FN_SHOT_HELLFIRE_SHADOW, true );
153       generatesSmokePuffs = true;
154       break;
155     }
156   case SHOT_MACHINE_GUN:
157     {
158       vel = Vector2D(VEL_SHOT_MACHINE_GUN, angle, POLAR);
159       timeToLive = LIFETIME_SHOT_MACHINE_GUN;
160       sprite = surfaceDB.loadSurface( FN_SHOT_MACHINE_GUN );
161       break;
162     }
163   case SHOT_ENERGY_BEAM:
164     {
165       vel = Vector2D(VEL_SHOT_ENERGY_BEAM, angle, POLAR);
166       timeToLive = LIFETIME_SHOT_ENERY_BEAM;
167       sprite = surfaceDB.loadSurface( FN_SHOT_ENERGY_BEAM, true );
168       break;
169     }
170 
171     // secondary shots heavy fighter
172   case SHOT_HF_DUMBFIRE:
173     {
174       vel = Vector2D(VEL_SHOT_HF_DUMBFIRE, angle, POLAR);
175       timeToLive = LIFETIME_SHOT_HF_DUMBFIRE;
176       sprite = surfaceDB.loadSurface( FN_SHOT_HF_DUMBFIRE );
177       generatesSmokePuffs = true;
178       break;
179     }
180   case SHOT_HF_DUMBFIRE_DOUBLE:
181     {
182       vel = Vector2D(VEL_SHOT_HF_DUMBFIRE_DOUBLE, angle, POLAR);
183       timeToLive = LIFETIME_SHOT_HF_DUMBFIRE_DOUBLE;
184       sprite = surfaceDB.loadSurface( FN_SHOT_HF_DUMBFIRE_DOUBLE );
185       generatesSmokePuffs = true;
186       break;
187     }
188   case SHOT_HF_KICK_ASS_ROCKET:
189     {
190       vel = Vector2D(VEL_SHOT_HF_KICK_ASS_ROCKET, angle, POLAR);
191       timeToLive = LIFETIME_SHOT_HF_KICK_ASS_ROCKET;
192       sprite = surfaceDB.loadSurface( FN_SHOT_HF_KICK_ASS_ROCKET );
193       spriteShadow = surfaceDB.loadSurface( FN_SHOT_HF_KICK_ASS_ROCKET_SHADOW, true );
194       break;
195     }
196   case SHOT_HF_LASER:
197     {
198       vel = Vector2D(VEL_SHOT_HF_LASER, angle, POLAR);
199       timeToLive = LIFETIME_SHOT_HF_LASER;
200       sprite = surfaceDB.loadSurface( FN_SHOT_HF_LASER );
201       break;
202     }
203 
204     // special shots
205   case SPECIAL_SHOT_HEATSEEKER:
206     {
207       vel = Vector2D(VEL_SPECIAL_SHOT_HEATSEEKER, angle, POLAR);
208       timeToLive = LIFETIME_SPECIAL_SHOT_HEATSEEKER;
209       sprite = surfaceDB.loadSurface( FN_SPECIAL_SHOT_HEATSEEKER );
210       generatesSmokePuffs = true;
211       break;
212     }
213   case SPECIAL_SHOT_NUKE:
214     {
215       vel = Vector2D( VEL_SPECIAL_SHOT_NUKE, angle, POLAR );
216       timeToLive = LIFETIME_SPECIAL_SHOT_NUKE;
217       sprite = surfaceDB.loadSurface( FN_SPECIAL_SHOT_NUKE );
218       spriteShadow = surfaceDB.loadSurface( FN_SPECIAL_SHOT_NUKE_SHADOW, true );
219       break;
220     }
221 
222     // enemy shots
223   case ENEMY_SHOT_NORMAL:
224     {
225       vel = Vector2D(VEL_ENEMY_SHOT_NORMAL, angle, POLAR);
226       timeToLive = LIFETIME_ENEMY_SHOT_NORMAL;
227       sprite = surfaceDB.loadSurface( FN_ENEMY_SHOT_NORMAL );
228       break;
229     }
230   case ENEMY_SHOT_TANK_ROCKET:
231     {
232       vel = Vector2D(VEL_ENEMY_SHOT_TANK_ROCKET, angle, POLAR);
233       timeToLive = LIFETIME_ENEMY_SHOT_TANK_ROCKET;
234       sprite = surfaceDB.loadSurface( FN_ENEMY_SHOT_TANK_ROCKET );
235       spriteShadow = surfaceDB.loadSurface( FN_ENEMY_SHOT_TANK_ROCKET_SHADOW, true );
236       generatesSmokePuffs = true;
237       break;
238     }
239 
240   default:
241     {
242       vel = Vector2D(0,0);
243       timeToLive = 0;
244       sprite = surfaceDB.loadSurface( FN_SHOT_NORMAL );
245       break;
246     }
247   }
248 
249 }
250 
251 
moveAndCollide(int dT)252 void Shot::moveAndCollide( int dT ) {
253 
254   if ( fromWhichPlayer == 666 ) {
255     moveAndCollideEnemyShot( dT );
256   } else {
257     moveAndCollidePlayerShot( dT );
258   }
259 
260   generateSmokePuff( dT );
261 
262   timeToLive -= dT;
263 }
264 
265 
moveAndCollidePlayerShot(int dT)266 void Shot::moveAndCollidePlayerShot( int dT ) {
267 
268   Vector2D posOld = pos;
269 
270   // move the shot
271   movePlayerShot( dT );
272 
273   if ( !isExpired() && collidePlayerShot( posOld ) ) {
274     addExplosion();
275   }
276 }
277 
278 
movePlayerShot(int dT)279 void Shot::movePlayerShot( int dT ) {
280   switch (shotType) {
281   case SHOT_NORMAL:
282   case SHOT_NORMAL_HEAVY:
283   case SHOT_DOUBLE:
284   case SHOT_DOUBLE_HEAVY:
285   case SHOT_TRIPLE:
286   case SHOT_HF_NORMAL:
287   case SHOT_HF_DOUBLE:
288   case SHOT_HF_TRIPLE:
289   case SHOT_HF_QUATTRO:
290   case SHOT_HF_QUINTO:
291   case SHOT_DUMBFIRE:
292   case SHOT_DUMBFIRE_DOUBLE:
293   case SHOT_KICK_ASS_ROCKET:
294   case SHOT_MACHINE_GUN:
295   case SHOT_ENERGY_BEAM:
296   case SHOT_HF_DUMBFIRE:
297   case SHOT_HF_DUMBFIRE_DOUBLE:
298   case SHOT_HF_KICK_ASS_ROCKET:
299   case SHOT_HF_LASER:
300     {
301       pos = pos + vel * dT / 1000.0;
302       break;
303     }
304   case SHOT_HELLFIRE:
305     {
306       pos = pos + vel * dT / 1000.0;
307       if ( timeToLive < LIFETIME_SHOT_HELLFIRE - 100 ) {
308 	vel = Vector2D( 0, -VEL_SHOT_HELLFIRE );
309       }
310       break;
311     }
312   case SPECIAL_SHOT_HEATSEEKER:
313     {
314       pos = pos + vel * dT / 1000.0;
315       if ( timeToLive >= LIFETIME_SPECIAL_SHOT_HEATSEEKER - 200 ) break;
316       int idxNearestEnemy = 0;
317       float distNearest = 10000;
318       for ( unsigned int i = 0; i < enemys->getNrEnemys(); i++ ) {
319 	// vector from the shotPosition to the enemy
320 	Vector2D v = enemys->getEnemy(i)->getPos() - pos;
321 	if ( getAbsAngleDifference( vel.getDirection(), v.getDirection() ) < 180 ) {
322 	  float dist = (enemys->getEnemy(i)->getPos() - pos).getLength();
323 	  if ( dist < distNearest ) {
324 	    distNearest = dist;
325 	    idxNearestEnemy = i;
326 	  }
327 	}
328       }
329       if ( distNearest != 10000 ) {
330 	float angle =
331 	  getAngleDifference( (enemys->getEnemy(idxNearestEnemy)->getPos()-pos).getDirection(),
332 			      vel.getDirection() );
333 	if ( fabs(angle) < 80 * dT / 1000.0 ) {
334 	  vel.rotate(angle);
335 	} else if ( angle < 0 ) {
336 	  vel.rotate( -80 * dT / 1000.0 );
337 	} else {
338 	  vel.rotate( 80 * dT / 1000.0 );
339 	}
340       }
341 
342       break;
343     }
344 
345   case SPECIAL_SHOT_NUKE:
346     {
347       pos = pos + vel * dT / 1000.0;
348       // Nuke is in its place!
349       if ( (pos - Vector2D( SCREEN_WIDTH / 2.0, SCREEN_HEIGHT / 2.0 )).getLength()
350 	   <= (vel * dT / 1000.0).getLength() ) {
351 	nukeIsInPlace = true;
352       }
353       break;
354     }
355 
356   default:
357     {
358       cout << "movePlayerShot: unexpected shotType: " << shotType << endl;
359       break;
360     }
361   }
362 
363   // clip at the outside of the window
364   if ( !RectangleGeo( Vector2D( -SHOT_SCREEN_BORDER, -SHOT_SCREEN_BORDER ),
365 		      Vector2D( SCREEN_WIDTH + SHOT_SCREEN_BORDER,
366 				SCREEN_HEIGHT + SHOT_SCREEN_BORDER )).isInside(pos) ) {
367     timeToLive = 0;
368   }
369 }
370 
371 
collidePlayerShot(Vector2D posOld)372 bool Shot::collidePlayerShot( Vector2D posOld ) {
373   switch (shotType) {
374     // only against air
375   case SHOT_ENERGY_BEAM:
376     {
377       BoundingBox box( lroundf(posOld.getX()) - sprite->w / 2,
378 		       lroundf(pos.getY()) - sprite->w / 2,
379 		       sprite->w,
380 		       lroundf((posOld-pos).getY()) + sprite->h );
381       for ( unsigned int i = 0; i < enemys->getNrEnemys(); i++ ) {
382 	if ( ENEMY_FLYING[ enemys->getEnemy(i)->getType() ] &&
383 	     enemys->getEnemy(i)->collidesWith( &box ) ) {
384 	  enemys->getEnemy(i)->doDamage( shotType, fromWhichPlayer );
385 	  timeToLive = 0;
386 	  collidedWithGround = !ENEMY_FLYING[ enemys->getEnemy(i)->getType() ];
387 	  return true;
388 	}
389       }
390       break;
391     }
392     //only against air
393   case SHOT_HF_LASER:
394     {
395       for ( unsigned int i = 0; i < enemys->getNrEnemys(); i++ ) {
396 	if ( ENEMY_FLYING[ enemys->getEnemy(i)->getType() ] &&
397 	     enemys->getEnemy(i)->collidesWith( posOld, pos ) ) {
398 	  enemys->getEnemy(i)->doDamage( shotType, fromWhichPlayer );
399 	  timeToLive = 0;
400 	  collidedWithGround = false;
401 	  return true;
402 	}
403       }
404       break;
405     }
406   // against air and ground
407   case SHOT_NORMAL:
408   case SHOT_NORMAL_HEAVY:
409   case SHOT_DOUBLE:
410   case SHOT_DOUBLE_HEAVY:
411   case SHOT_TRIPLE:
412   case SHOT_HF_NORMAL:
413   case SHOT_HF_DOUBLE:
414   case SHOT_HF_TRIPLE:
415   case SHOT_HF_QUATTRO:
416   case SHOT_HF_QUINTO:
417     {
418       for ( unsigned int i = 0; i < enemys->getNrEnemys(); i++ ) {
419 	if ( //ENEMY_FLYING[ enemys->getEnemy(i)->getType() ] &&
420 	     enemys->getEnemy(i)->collidesWith( posOld, pos ) ) {
421 	  enemys->getEnemy(i)->doDamage( shotType, fromWhichPlayer );
422 	  timeToLive = 0;
423 	  collidedWithGround = !ENEMY_FLYING[ enemys->getEnemy(i)->getType() ];
424 	  return true;
425 	}
426       }
427       break;
428     }
429     // against air and ground
430   case SHOT_DUMBFIRE:
431   case SHOT_DUMBFIRE_DOUBLE:
432   case SHOT_HF_DUMBFIRE:
433   case SHOT_HF_DUMBFIRE_DOUBLE:
434     {
435       for ( unsigned int i = 0; i < enemys->getNrEnemys(); i++ ) {
436 	if ( enemys->getEnemy(i)->collidesWith( Circle(pos, 15) ) ) {
437 	  enemys->getEnemy(i)->doDamage( shotType, fromWhichPlayer );
438 	  timeToLive = 0;
439 	  collidedWithGround = !ENEMY_FLYING[ enemys->getEnemy(i)->getType() ];
440 	  return true;
441 	}
442       }
443       break;
444     }
445     // only against ground
446   case SHOT_KICK_ASS_ROCKET:
447   case SHOT_HF_KICK_ASS_ROCKET:
448     {
449       for ( unsigned int i = 0; i < enemys->getNrEnemys(); i++ ) {
450 	if ( (!ENEMY_FLYING[ enemys->getEnemy(i)->getType() ]) &&
451 	     enemys->getEnemy(i)->collidesWith( Circle(pos, 15) ) ) {
452 	  enemys->getEnemy(i)->doDamage( shotType, fromWhichPlayer );
453 	  timeToLive = 0;
454 	  collidedWithGround = true;
455 	  return true;
456 	}
457       }
458       break;
459     }
460     // only against ground, but has to hit more exactly than kickAssRocket
461   case SHOT_HELLFIRE:
462     {
463       for ( unsigned int i = 0; i < enemys->getNrEnemys(); i++ ) {
464 	if ( (!ENEMY_FLYING[ enemys->getEnemy(i)->getType() ]) &&
465 	     enemys->getEnemy(i)->collidesWith( Circle(pos, 5) ) ) {
466 	  enemys->getEnemy(i)->doDamage( shotType, fromWhichPlayer );
467 	  timeToLive = 0;
468 	  collidedWithGround = true;
469 	  return true;
470 	}
471       }
472       break;
473     }
474     // against air and ground
475   case SHOT_MACHINE_GUN:
476     {
477       for ( unsigned int i = 0; i < enemys->getNrEnemys(); i++ ) {
478 	if ( enemys->getEnemy(i)->collidesWith( posOld, pos ) ) {
479 	  enemys->getEnemy(i)->doDamage( shotType, fromWhichPlayer );
480 	  timeToLive = 0;
481 	  collidedWithGround = !ENEMY_FLYING[ enemys->getEnemy(i)->getType() ];
482 	  return true;
483 	}
484       }
485       break;
486     }
487 
488     // against air and ground
489   case SPECIAL_SHOT_HEATSEEKER:
490     {
491       for ( unsigned int i = 0; i < enemys->getNrEnemys(); i++ ) {
492 	if ( enemys->getEnemy(i)->collidesWith( Circle(pos, 5) ) ) {
493 	  enemys->getEnemy(i)->doDamage( shotType, fromWhichPlayer );
494 	  timeToLive = 0;
495 	  collidedWithGround = !ENEMY_FLYING[ enemys->getEnemy(i)->getType() ];
496 	  return true;
497 	}
498       }
499       break;
500     }
501   case SPECIAL_SHOT_NUKE: break;
502 
503   default:
504     {
505       cout << "collidePlayerShot: unexpected shotType: " << shotType << endl;
506       return false;
507     }
508   }
509   return false;
510 }
511 
512 ///////////////////////////
513 
moveAndCollideEnemyShot(int dT)514 void Shot::moveAndCollideEnemyShot( int dT ) {
515   Vector2D posOld = pos;
516 
517   moveEnemyShot( dT );
518 
519   if ( !isExpired() && collideEnemyShot( posOld ) ) {
520     addExplosion();
521   }
522 }
523 
524 
moveEnemyShot(int dT)525 void Shot::moveEnemyShot( int dT ) {
526   switch (shotType) {
527   case ENEMY_SHOT_NORMAL: {
528     // is this shot near the deflector of a racer?
529     for ( unsigned int i = 0; i < racers->getNrRacers(); i++) {
530       if ( racers->getRacer(i)->getShipType() == HEAVY_FIGHTER ) {
531         Vector2D racerToShot = pos - racers->getRacer(i)->getPos();
532         if ( racerToShot.getLength() < RACER_DEFLECTOR_ACTIVATION_DIST ) {
533           vel += Vector2D( RACER_DEFLECTOR_POWER * (dT / 1000.0),
534 			   racerToShot.getDirection(), POLAR );
535         }
536         if ( racers->getRacer(i)->isDeflectorSpecialActive() ) {
537           if ( racerToShot.getLength() < ITEM_DEFLECTOR_ACTIVATION_DIST ) {
538             vel += Vector2D( ITEM_DEFLECTOR_POWER * (dT / 1000.0),
539 			     racerToShot.getDirection(), POLAR );
540           }
541         }
542       }
543     }
544     pos = pos + vel * dT / 1000.0;
545     break;
546   }
547   case ENEMY_SHOT_TANK_ROCKET: {
548     if ( deflectedBySonicFromPlayer1 ) {
549       Vector2D racerToShot = pos - racers->getRacer(0)->getPos();
550       if ( racerToShot.getLength() < RACER_SONIC_ACTIVATION_DIST ) {
551         vel += Vector2D( RACER_SONIC_POWER * (dT / 1000.0),
552 			 racerToShot.getDirection(), POLAR );
553         deflectedBySonicFromPlayer1 = false;
554       }
555     }
556     if ( deflectedBySonicFromPlayer2 ) {
557       Vector2D racerToShot = pos - racers->getRacer(1)->getPos();
558       if ( racerToShot.getLength() < RACER_SONIC_ACTIVATION_DIST ) {
559         vel += Vector2D( RACER_SONIC_POWER * (dT / 1000.0),
560 			 racerToShot.getDirection(), POLAR );
561         deflectedBySonicFromPlayer2 = false;
562       }
563     }
564     pos = pos + vel * dT / 1000.0;
565     break;
566   }
567   default: {
568     cout << "moveEnemyShot: unexpected shotType: " << shotType << endl;
569     break;
570   }
571   }
572 
573   // clip at the outside of the window
574   if ( !RectangleGeo(Vector2D( -SHOT_SCREEN_BORDER, -SHOT_SCREEN_BORDER ),
575 		     Vector2D( SCREEN_WIDTH + SHOT_SCREEN_BORDER,
576 			       SCREEN_HEIGHT + SHOT_SCREEN_BORDER )).isInside(pos) ) {
577     timeToLive = 0;
578   }
579 }
580 
581 
collideEnemyShot(Vector2D posOld)582 bool Shot::collideEnemyShot( Vector2D posOld ) {
583   switch (shotType) {
584   case ENEMY_SHOT_NORMAL:
585     {
586       for ( unsigned int i = 0; i < racers->getNrRacers(); i++ ) {
587 	if ( racers->getRacer(i)->collidesWith( posOld, pos ) ) {
588 	  racers->getRacer(i)->doDamage( shotType );
589 	  timeToLive = 0;
590 	  collidedWithGround = false;
591 	  return true;
592 	}
593       }
594       break;
595     }
596   case ENEMY_SHOT_TANK_ROCKET:
597     {
598       for ( unsigned int i = 0; i < racers->getNrRacers(); i++ ) {
599 	if ( racers->getRacer(i)->collidesWith( Circle(pos, 5) ) ) {
600 	  racers->getRacer(i)->doDamage( shotType );
601 	  timeToLive = 0;
602 	  collidedWithGround = false;
603 	  return true;
604 	}
605       }
606       break;
607     }
608   default:
609     {
610       cout << "collideEnemyShot: unexpected shotType: " << shotType << endl;
611       return false;
612     }
613   }
614   return false;
615 }
616 
617 /////////////
618 
addExplosion()619 void Shot::addExplosion() {
620   Explosion *explosion;
621   switch (shotType) {
622   default:
623     {
624       if ( collidedWithGround ) {
625 	explosion = new Explosion( FN_EXPLOSION_NORMAL, pos,
626 				   vel / 10.0, EXPLOSION_NORMAL_GROUND );
627       } else {
628 	if ( shotType == SHOT_HF_LASER ) {
629 	  // Laser is too fast...
630 	  explosion = new Explosion( FN_EXPLOSION_NORMAL, pos,
631 				     vel / 30.0, EXPLOSION_NORMAL_AIR );
632 	} else {
633 	  explosion = new Explosion( FN_EXPLOSION_NORMAL, pos,
634 				     vel / 10.0, EXPLOSION_NORMAL_AIR );
635 	}
636       }
637       break;
638     }
639   }
640   explosions->addExplosion( explosion );
641 }
642 
643 ////////////////////
644 
drawShadow(SDL_Surface * screen)645 void Shot::drawShadow(SDL_Surface *screen) {
646   switch (shotType) {
647   case SHOT_KICK_ASS_ROCKET:
648   case SHOT_HF_KICK_ASS_ROCKET:
649   case SHOT_HELLFIRE:
650   case SPECIAL_SHOT_NUKE:
651     {
652       SDL_Rect shadowR;
653       shadowR.x = lroundf(pos.getX()) - spriteShadow->w / 2 - 7;
654       shadowR.y = lroundf(pos.getY()) - spriteShadow->h / 2 + 7;
655       shadowR.w = spriteShadow->w;
656       shadowR.h = spriteShadow->h;
657       SDL_BlitSurface( spriteShadow, 0, screen, &shadowR );
658       break;
659     }
660   case ENEMY_SHOT_TANK_ROCKET:
661     {
662       SDL_Rect destR;
663       SDL_Rect srcR;
664       destR.x = lroundf(pos.getX()) - spriteShadow->w / 16 - 10;
665       destR.y = lroundf(pos.getY()) - spriteShadow->h / 2 + 10;
666       destR.w = spriteShadow->w / 8;
667       destR.h = spriteShadow->h;
668       float angle = vel.getDirection() + 202.5;
669       int idx = lroundf(angle) % 360;
670       idx = idx / 45;
671       srcR.x = idx * spriteShadow->w / 8;
672       srcR.y = 0;
673       srcR.w = spriteShadow->w / 8;
674       srcR.h = spriteShadow->h;
675 
676       SDL_BlitSurface( spriteShadow, &srcR, screen, &destR );
677       break;
678     }
679   default: break;
680   }
681 }
682 
drawGroundShot(SDL_Surface * screen)683 void Shot::drawGroundShot(SDL_Surface *screen) {
684   switch (shotType) {
685   case SHOT_KICK_ASS_ROCKET:
686   case SHOT_HF_KICK_ASS_ROCKET:
687   case SHOT_HELLFIRE:
688     {
689       SDL_Rect destR;
690       SDL_Rect srcR;
691       destR.x = lroundf(pos.getX()) - sprite->w / 2;
692       destR.y = lroundf(pos.getY()) - sprite->h / 2;
693       destR.w = sprite->w;
694       destR.h = sprite->h;
695       srcR.x = 0;
696       srcR.y = 0;
697       srcR.w = sprite->w;
698       srcR.h = sprite->h;
699 
700       SDL_BlitSurface( sprite, &srcR, screen, &destR );
701       break;
702     }
703   default: break;
704   }
705 }
706 
drawGroundAirShot(SDL_Surface * screen)707 void Shot::drawGroundAirShot(SDL_Surface *screen) {
708   switch (shotType) {
709   case SHOT_DUMBFIRE:
710   case SHOT_DUMBFIRE_DOUBLE:
711   case SHOT_HF_DUMBFIRE:
712   case SHOT_HF_DUMBFIRE_DOUBLE:
713     {
714       SDL_Rect destR;
715       SDL_Rect srcR;
716       destR.x = lroundf(pos.getX()) - sprite->w / 16;
717       destR.y = lroundf(pos.getY()) - sprite->h / 2;
718       destR.w = sprite->w / 8;
719       destR.h = sprite->h;
720       // TODO: eight directions are outdated for dumbfire, but existent in the image
721       float angle = vel.getDirection() + 202.5;
722       int idx = lroundf(angle) % 360;
723       idx = idx / 45;
724       srcR.x = idx * 8;
725       srcR.y = 0;
726       srcR.w = 8;
727       srcR.h = 8;
728 
729       SDL_BlitSurface( sprite, &srcR, screen, &destR );
730       break;
731     }
732   case SHOT_MACHINE_GUN:
733   case SHOT_HF_LASER:
734     {
735       SDL_Rect destR;
736       destR.x = lroundf(pos.getX()) - sprite->w / 2;
737       destR.y = lroundf(pos.getY()) - sprite->h / 2;
738       destR.w = sprite->w;
739       destR.h = sprite->h;
740       SDL_BlitSurface( sprite, 0, screen, &destR );
741       break;
742     }
743   case SHOT_ENERGY_BEAM:
744     {
745       SDL_Rect destR;
746       destR.x = lroundf(pos.getX()) - sprite->w / 2;
747       destR.y = lroundf(pos.getY()) - sprite->h / 2;
748       destR.w = sprite->w;
749       destR.h = sprite->h;
750       SDL_BlitSurface( sprite, 0, screen, &destR );
751       destR.x = lroundf(pos.getX()) - sprite->w / 2;
752       destR.y = lroundf(pos.getY()) - sprite->h / 2;
753       SDL_BlitSurface( sprite, 0, screen, &destR );
754       break;
755     }
756   case SPECIAL_SHOT_HEATSEEKER:
757     {
758       SDL_Rect destR;
759       SDL_Rect srcR;
760       destR.x = lroundf(pos.getX()) - sprite->w / 16;
761       destR.y = lroundf(pos.getY()) - sprite->h / 2;
762       destR.w = sprite->w / 8;
763       destR.h = sprite->h;
764 
765       float angle = vel.getDirection() + 202.5;
766       int idx = lroundf(angle) % 360;
767       idx = idx / 45;
768       srcR.x = idx * 8;
769       srcR.y = 0;
770       srcR.w = 8;
771       srcR.h = 8;
772 
773       SDL_BlitSurface( sprite, &srcR, screen, &destR );
774       break;
775     }
776   case ENEMY_SHOT_TANK_ROCKET:
777     {
778       SDL_Rect destR;
779       SDL_Rect srcR;
780       destR.x = lroundf(pos.getX()) - sprite->w / 16;
781       destR.y = lroundf(pos.getY()) - sprite->h / 2;
782       destR.w = sprite->w / 8;
783       destR.h = sprite->h;
784 
785       float angle = vel.getDirection() + 202.5;
786       int idx = lroundf(angle) % 360;
787       idx = idx / 45;
788       srcR.x = idx * sprite->w / 8;
789       srcR.y = 0;
790       srcR.w = sprite->w / 8;
791       srcR.h = sprite->h;
792 
793       SDL_BlitSurface( sprite, &srcR, screen, &destR );
794       break;
795     }
796 
797   default: break;
798   }
799 }
800 
drawAirShot(SDL_Surface * screen)801 void Shot::drawAirShot(SDL_Surface *screen) {
802   switch (shotType) {
803   case SHOT_NORMAL:
804   case SHOT_NORMAL_HEAVY:
805   case SHOT_DOUBLE:
806   case SHOT_DOUBLE_HEAVY:
807   case SHOT_TRIPLE:
808   case SHOT_HF_NORMAL:
809   case SHOT_HF_DOUBLE:
810   case SHOT_HF_TRIPLE:
811   case SHOT_HF_QUATTRO:
812   case SHOT_HF_QUINTO:
813   case SPECIAL_SHOT_NUKE:
814   case ENEMY_SHOT_NORMAL:
815     {
816       SDL_Rect destR;
817       destR.x = lroundf(pos.getX()) - sprite->w / 2;
818       destR.y = lroundf(pos.getY()) - sprite->h / 2;
819       destR.w = sprite->w;
820       destR.h = sprite->h;
821       SDL_BlitSurface( sprite, 0, screen, &destR );
822       break;
823     }
824   default: break;
825   }
826 }
827 
getShotType()828 ShotTypes Shot::getShotType() {
829   return shotType;
830 }
831 
832 
generateSmokePuff(int dT)833 void Shot::generateSmokePuff( int dT) {
834   if ( ! generatesSmokePuffs ) return;
835 
836   timeToNextSmokePuff -= dT;
837   if ( timeToNextSmokePuff < 0 ) {
838     Vector2D relPos = -vel;
839     relPos.setLength( sprite->h / 2 );
840     if ( shotType == SHOT_HELLFIRE ||
841 	 shotType == ENEMY_SHOT_TANK_ROCKET ) {
842 
843       smokePuffs->addSmokePuff( pos + relPos,
844 				vel * SMOKE_PUFF_VELOCITY_FACTOR,
845 				SMOKE_PUFF_MEDIUM );
846       timeToNextSmokePuff += SMOKE_PUFF_DELAY_TO_NEXT_PUFF[ SMOKE_PUFF_MEDIUM ];
847     } else {
848       smokePuffs->addSmokePuff( pos + relPos,
849 				vel * SMOKE_PUFF_VELOCITY_FACTOR,
850 				SMOKE_PUFF_SMALL );
851       timeToNextSmokePuff += SMOKE_PUFF_DELAY_TO_NEXT_PUFF[ SMOKE_PUFF_SMALL ];
852     }
853   }
854 }
855 
856 
857 
858