1 /* $Id: homingmissile.cpp,v 1.23.4.1 2006/01/20 11:33:52 chfreund Exp $ */
2 
3 #include "homingmissile.hpp"
4 #include "avatar.hpp"
5 #include "player.hpp"
6 #include "world.hpp"
7 #include "smoke.hpp"
8 
9 /**********************************************************/
10 
11 #define  ACCELERATE_FUEL         10.0
12 #define  MIN_ACCELERATION         5
13 #define  CHASE_FUEL             100.0
14 #define  CHASING_THRUST_IMPACT  100.0
15 #define  RANGE_OF_SIGHT         220.0
16 #define  SPEED_PREDICTION         3.0
17 // dependent constants
18 #define  TOTAL_FUEL             (ACCELERATE_FUEL + CHASE_FUEL)
19 #define  RANGE_OF_SIGHT_SQUARE  (RANGE_OF_SIGHT * RANGE_OF_SIGHT)
20 
21 // "nearly 1.0". Since the accumulated time is incremented in each
22 // call of update, there might be some roundoff errors. To make sure
23 // that the condition "if(accumulatedDT >= DELTA_T_PER_ANIMATION_STEP)"
24 // is true whenever it should be true, we use 0.99 instead of 1.0
25 #define  DELTA_T_PER_ANIMATION_STEP  0.99
26 
27 /**********************************************************/
28 
HomingMissile()29 HomingMissile::HomingMissile()
30 {
31 //
32 // take the values from class Missile
33 //
34 //	m_health = 300000;
35 	m_mass = 100.0;
36 //
37 //	m_collRectX = -4;
38 //	m_collRectY = -4;
39 //
40 //	m_collRectWidth  = 9;
41 //	m_collRectHeight = 9;
42 //
43 }
44 
45 /**********************************************************/
46 
initialize(const Vector & pos,const Vector & vel,const Uint8 owner,const Sint32 nShrapnels,const Sint32 damage,const Vector & thrust)47 bool HomingMissile::initialize( const Vector& pos,
48                                 const Vector& vel,
49                                 const Uint8   owner,
50                                 const Sint32  nShrapnels,
51                                 const Sint32  damage,
52                                 const Vector& thrust )
53 {
54 	return
55 	Missile::initialize( pos, vel, owner, nShrapnels,
56 	                     damage, TOTAL_FUEL, thrust );
57 }
58 
59 /**********************************************************/
60 
update()61 void HomingMissile::update()
62 {
63 	preBallistics();
64 
65 	if( m_health <= 0 ) {
66 		explode();
67 	}
68 	else {
69 		// check for prey
70 		const std::vector<Player*>& players = m_worldPointer->getPlayerList();
71 		const Avatar* prey = NULL;
72 		real minDist2 = RANGE_OF_SIGHT_SQUARE;
73 		// only check for prey, if we could chase it
74 		if( !m_accelTimer.isElapsed() ) {
75 			for( Uint8 p = 0; p < players.size(); p++ ) {
76 				// do not chase the missile's owner
77 				if( m_owner != players[p]->getPlayerID() ) {
78 					const Avatar* avatar   = players[p]->getAvatar();
79 					if( avatar->isLiving() && avatar->isWeaponVisible() ) {
80 						const real dist2 = (avatar->getPos() - m_pos).abs2();
81 						// search the nearest avatar
82 						// NOTE that due to the initialization of minDistSquare
83 						// distsquare will also be < RANGE_OF_SIGHT_SQUARE.
84 						if( dist2 < minDist2 ) {
85 							minDist2 = dist2;
86 							prey          = avatar;
87 						}
88 					}
89 				}
90 			}
91 		}
92 
93 		bool ignition = false;
94 		// we found a prey
95 		if( prey != NULL ) {
96 			// if we still have fuel AND
97 			if( !m_accelTimer.isElapsed() ) {
98 				// we are not in the initial start phase
99 				if( m_accelTimer.getTimeToLive() < (TOTAL_FUEL - MIN_ACCELERATION) ) {
100 					// turn thrust
101 					m_thrust = prey->getPos();
102 					m_thrust += prey->getVel() * SPEED_PREDICTION;
103 					m_thrust -= m_pos;
104 					m_thrust -= m_vel * SPEED_PREDICTION;
105 					m_thrust.normalize() *= CHASING_THRUST_IMPACT;
106 					// ignition towards the prey
107 					ignition = true;
108 				} else {
109 					// ignition in the initial start phase
110 					ignition = true;
111 					prey     = NULL;
112 				}
113 			}
114 		// we did not find a prey
115 		} else {
116 			// we are still in the neutral acceleration phase
117 			if( m_accelTimer.getTimeToLive() > CHASE_FUEL ) {
118 				ignition = true;
119 			}
120 		}
121 
122 		// engine is running
123 		if( ignition ) {
124 			// accelerate in direction of thrust
125 			m_force += m_thrust;
126 			if ( m_accelTimer.hasSignal() ) {
127 				// throw out some booster sparks
128 				for( int i = 0; i < 15; i++ ) {
129 					m_worldPointer->getParticles()->addParticle( Particles::ParticleData(
130 						m_pos,
131 						m_vel - m_thrust*0.1 + m_worldPointer->getRandom().getVector( 1.5 ),
132 						Particles::BOUNCE_EARTH | //Particles::BOUNCE_OBJECT |
133 						Particles::REDUNDANT | Particles::SPARK, 2, 0,
134 						Particles::m_N_SPARK_COLORS/2 + m_worldPointer->getRandom().getUint32() % (Particles::m_N_SPARK_COLORS/2+1), 1,
135 						0xffffff));
136 				}
137 				// generate a smoke cloud
138 				Smoke* cloud = static_cast<Smoke*>(m_worldPointer->newObject(SMOKE_30));
139 				cloud->setPos( m_pos + localRnd.getVector( 4.0 ));
140 				cloud->setVel( m_vel + localRnd.getVector( 2.0 ));
141 				// color the cloud, if we chase someone
142 				if( prey != NULL ) {
143 					cloud->setColor( prey->getPlayerColor() );
144 					// lock the avatar (maybe it has got some authomatic defense)
145 					((Avatar*)prey)->getLockedByHomingMissile( this, minDist2 );
146 				}
147 			}
148 			// decrement "fuel"
149 			m_accelTimer.countDown( m_worldPointer->getDT( m_pos ) );
150 		}
151 
152 		doBallistics();
153 
154 		// eventually increment the animation frame
155 		m_AccumulatedDT += m_worldPointer->getDT( m_pos );
156 		if( m_AccumulatedDT >= DELTA_T_PER_ANIMATION_STEP ) {
157 			m_AccumulatedDT = 0.0;
158 			if( ++m_Frame >= m_SequenceLength ) m_Frame = 0;
159 		}
160 		// select new missile sprite
161 		updateSpriteSelection( ignition );
162 
163 		postBallistics();
164 	}
165 }
166 
167 /**********************************************************/
168 
explode()169 void HomingMissile::explode() {
170 	Audio::getInstance()->playSound( m_explodeSample, m_pos );
171 	m_worldPointer->newObject( EXPLOSION_80 )->setPos( m_pos );
172 	for( int s = 0; s < m_nShrapnels; s++ ) {
173 		m_worldPointer->getParticles()->addParticle( Particles::ParticleData( m_pos,
174 		     m_worldPointer->getRandom().getVector( 15 + 5*m_worldPointer->getRandom().getNormedReal() ), // + getSpeed(),  otherwise shrapnels will just fly forward
175 		     Particles::EXPL_EARTH | Particles::EXPL_OBJECT | Particles::DAMAGE |
176 		     Particles::INVISIBLE,
177 		     0, m_owner, 2, m_damage, 0xffffff ));
178 	}
179 
180 	m_removeMeAfterUpdate = true;
181 }
182 
183 /**********************************************************/
184 
updateSpriteSelection(const bool activeEngine)185 void HomingMissile::updateSpriteSelection( const bool activeEngine )
186 {
187 	// If the engine is running, face in the negative direction
188 	// of the thrust vector, otherwise in direction of movement.
189 	const real angle = (activeEngine
190 	                    ? ATAN2_REAL( m_thrust.y, m_thrust.x )
191 	                    : ATAN2_REAL( m_vel.y,    m_vel.x   ));
192 	// set the sequence index.
193 	// NOTE: setSequence also updates m_SequenceLength.
194 	setSequence( (ROUND( angle*(18.0/M_PI) - 0.5 )+36) % 36 );
195 
196 	// only reset the frame index, if we cannot continue
197 	// this movement in a different aiming angle
198 	if( getFrame() >= m_SequenceLength )  setFrame( 0 );
199 }
200 
201 /**********************************************************/
202 
dump(std::ostream & out) const203 void HomingMissile::dump( std::ostream& out ) const {
204 	using namespace std;
205 	out << "### HomingMissile object ###" << endl;
206 	Missile::dump( out );
207 }
208