1 /* $Id: avatarworm.cpp,v 1.74.2.1 2006/03/14 14:45:52 chfreund Exp $ */
2 
3 #include  "avatarworm.hpp"
4 #include  "audio.hpp"
5 #include  "random.hpp"
6 
7 /**********************************************************/
8 // some graphic settings we cannot extract from the sprites
9 #define  MIN_AIMING_ANGLE     0
10 #define  AIMING_ANGLE_STEP    5
11 /**********************************************************/
12 // derived constants
13 // number of aiming angles
14 #define  NUM_AIMING_ANGLES  (( ((Sint32)MAX_AIMING_ANGLE - MIN_AIMING_ANGLE) / \
15                                 AIMING_ANGLE_STEP ) + 1)
16 // number of sequences for one direction (two directions: 0,1)
17 #define  NUM_SEQUENCES_PER_DIRECTION  NUM_AIMING_ANGLES
18 // number of walking sequences
19 #define  NUM_WALKING_SEQUENCES  (2 * NUM_SEQUENCES_PER_DIRECTION)
20 //////////////////////////////////////////////////
21 // more settings concerning the graphics sequence indices
22 //
23 // first sequence belonging to gun occupation
24 #define  FIRST_GUN_SEQUENCE      NUM_WALKING_SEQUENCES
25 #define  NUM_GUN_SEQUENCES       1
26 #define  LAST_GUN_SEQUENCE      (FIRST_GUN_SEQUENCE + NUM_GUN_SEQUENCES - 1)
27 // first sequence showing the worm operating a remote control
28 #define  FIRST_RC_SEQUENCE      (FIRST_GUN_SEQUENCE + NUM_GUN_SEQUENCES)
29 #define  NUM_RC_SEQUENCES        2
30 /**********************************************************/
31 
AvatarWorm()32 AvatarWorm::AvatarWorm()
33 		  : Avatar()
34 {
35 	String sampleFilename;
36 
37 	m_maxHealth      = 1000000;
38 	m_health         = m_maxHealth;
39 	m_mass           = 250.0;
40 	m_collRectX      =  -4;
41 	m_collRectY      = -15;
42 	m_collRectWidth  =   9;
43 	m_collRectHeight =  21;
44 	m_weaponPivotDY  =   0;
45 
46 	// set the initial colors
47 	m_Colors[SKIN_COLOR] = TPLCOL_RGB( 255, 255, 255 );
48 	m_Colors[EYE_COLOR]  = TPLCOL_DONT_COL;
49 	m_Colors[GUN_COLOR]  = TPLCOL_RGB(   0, 100, 230 );
50 	// initialize the player and team color per default
51 	// for an individual fight
52 	m_Colors[PLAYER_COLOR] = m_Colors[SKIN_COLOR];
53 	m_Colors[TEAM_COLOR]   = m_Colors[GUN_COLOR];
54 
55 	for( int s = 0; s < N_INJURED_SAMPLES; s++ ) {
56 		sampleFilename.format( "sound/avatar/worm/injured%01i.wav", s+1 );
57 		m_injuredSample[s] = Audio::getInstance()->loadSound( sampleFilename );
58 	}
59 
60 	for( int s = 0; s < N_DIE_SAMPLES; s++ ) {
61 		sampleFilename.format( "sound/avatar/worm/die%02i.wav", s+1 );
62 		m_dieSample[s] = Audio::getInstance()->loadSound( sampleFilename );
63 	}
64 
65 }
66 
67 /**********************************************************/
68 
applyDamage(const Particles::ParticleData & p)69 int AvatarWorm::applyDamage( const Particles::ParticleData& p )
70 {
71 	const int damage = Avatar::applyDamage( p );
72 
73 	// \todo UGLY HACK!!! parts of this function should move to class Avatar
74 	if( damage <= 0 )  return 0;
75 
76 	LOG( 5 ) INFO( "Avatar::applyDamage: player '%s' was hit\n",
77 	               static_cast<char*>( getPlayer()->getName() ));
78 	Uint8 r, g, b;
79 	real brightness;
80 
81 	LOG( 5 ) INFO( "Avatar::applyDamage: damage: %i => new health: %i\n",
82 	                damage, m_health );
83 
84 	Player* const shooter = m_worldPointer->getPlayerByID( p.owner );
85 	if( shooter ) {
86 		m_worldPointer->getScorekeeper()
87 			->playerDamagedPlayer( shooter->getPlayerID(),
88 			                       getPlayer()->getPlayerID(),
89 			                       damage,
90 			                       m_health <= 0 );
91 	}
92 
93 
94 	if( m_health > 0 ) {
95 		// play sound
96 		if( m_damageSampleTimeout == 0 ) {
97 			Audio::getInstance()->playSound( m_injuredSample[m_worldPointer->getRandom().getUint32() % N_INJURED_SAMPLES],
98 			                                 ROUND( m_pos.x ), ROUND( m_pos.y ) );
99 			m_damageSampleTimeout = 5;
100 		}
101 
102 		// generate blood
103 		for( int i = 0; i < 3; i++ ) {
104 			brightness = 0.7 + 0.3*m_worldPointer->getRandom().getNormedReal();
105 			r = ROUND( static_cast<Uint8>( m_Colors[SKIN_COLOR] >> 16 ) * brightness );
106 			g = ROUND( static_cast<Uint8>( m_Colors[SKIN_COLOR] >>  8 ) * brightness );
107 			b = ROUND( static_cast<Uint8>( m_Colors[SKIN_COLOR]       ) * brightness );
108 
109 			Vector vel = m_vel;
110 			real velFactor = m_worldPointer->getRandom().getNormedReal();
111 			velFactor *= 0.20;
112 			velFactor += 0.10;
113 			vel += p.vel * velFactor;
114 			real length = m_worldPointer->getRandom().getNormedReal();
115 			length *= 2.0;
116 			vel += m_worldPointer->getRandom().getVector( length );
117 			m_worldPointer->getParticles()->addParticle( Particles::ParticleData( p.pos,
118 			     vel,
119 			     Particles::BOUNCE_EARTH | Particles::STAIN | Particles::REDUNDANT, 0,
120 			     0, 75, 1, (((r << 8) | g) << 8) | b ));
121 		}
122 	}
123 	else {
124 		// remove avatar stencil
125 		removeCollRect();
126 		setState( VISIBLE | WEAPON_VISIBLE | LIVING | SHOOTING | DIGGING, false );
127 
128 		// leave gun properly
129 		leaveCurrentGun();
130 
131 		// detach from everything
132 		detachFromAll();
133 
134 		// disable low-pass filter
135 		Audio::getInstance()->setLowPassFilter( -1.0 );
136 
137 		// play sound
138 		Audio::getInstance()->playSound( m_dieSample[m_worldPointer->getRandom().getUint32() % N_DIE_SAMPLES],
139 		                                 ROUND( m_pos.x ), ROUND( m_pos.y ) );
140 
141 		// add twice the current force to velocity
142 		real factor = m_worldPointer->getDT( m_pos );
143 		factor *= m_mass;
144 		factor *= 2.0;
145 		Vector velAdd = m_force;
146 		velAdd /= factor;
147 		m_vel += velAdd;
148 
149 		// generate a lot of blood
150 		for( int i = 0; i < 1000; i++ ) {
151 			brightness = 0.7 + 0.3*m_worldPointer->getRandom().getNormedReal();
152 			r = ROUND( static_cast<Uint8>( m_Colors[SKIN_COLOR] >> 16 ) * brightness );
153 			g = ROUND( static_cast<Uint8>( m_Colors[SKIN_COLOR] >>  8 ) * brightness );
154 			b = ROUND( static_cast<Uint8>( m_Colors[SKIN_COLOR]       ) * brightness );
155 
156 			Vector vel = m_vel;
157 			real length = m_worldPointer->getRandom().getNormedReal();
158 			length *= 4.0;
159 			length += 2.0;
160 			vel += m_worldPointer->getRandom().getVector( length );
161 			m_worldPointer->getParticles()->addParticle( Particles::ParticleData(
162 			     m_pos + elementProd( m_worldPointer->getRandom().getVector( 1.0 ), Vector( 3.0, 8.0 )),
163 				 vel,
164 				 Particles::BOUNCE_EARTH | Particles::BOUNCE_OBJECT | Particles::STAIN | Particles::REDUNDANT, 0,
165 			     0, 75, 1, (((r << 8) | g) << 8) | b ));
166 		}
167 
168 		// initialize eyes
169 		if( (m_worldPointer->getRandom().getUint32() & 15) == 0 ) {
170 			for( int eye = 0; eye < 2; eye++ ) {
171 				const Vector eyeVel = m_vel + m_worldPointer->getRandom().getVector( 2.0 );
172 				for( double y = m_pos.y-3.0; y <= m_pos.y+3.0; y++ ) {
173 					for( double x = m_pos.x-3.0; x <= m_pos.x+3.0; x++ ) {
174 						const double dist = (x-m_pos.x)*(x-m_pos.x) + (y-m_pos.y)*(y-m_pos.y);
175 						if( dist < 0.9*0.9 ) r = g = b = 0;
176 						else
177 						if( dist < 2.5*2.5 ) r = g = b = 230;
178 						else continue;
179 						m_worldPointer->getParticles()->addParticle( Particles::ParticleData(
180 						     Vector( x, y ),
181 						     eyeVel,
182 						     Particles::BOUNCE_EARTH | Particles::BOUNCE_OBJECT | Particles::STAIN | Particles::REDUNDANT, 0,
183 						     0, 200, 1, (((r << 8) | g) << 8) | b ));
184 					}
185 				}
186 			}
187 		}
188 
189 		// reset the bonus pack for the "sleeping state" of the avatar
190 		m_bonusPack.resetForRespawn();
191 
192 		// send message
193 		if ( shooter ) {
194 			if ( getPlayer() == shooter ) {
195 				m_messageSink->addMessage( MessageGenerator::createSuicideMessage( getPlayer()->getName() ) );
196 			}
197 			else {
198 				m_messageSink->addMessage( MessageGenerator::createFragMessage( shooter->getName(), getPlayer()->getName() ) );
199 			}
200 		}
201 	}
202 
203 	return damage;
204 }
205 
206 /**********************************************************/
207 
applyDamage(const int damage,Player * shooter)208 int AvatarWorm::applyDamage( const int damage, Player* shooter ) {
209 	const int realDamage = Avatar::applyDamage( damage, shooter );
210 
211 	if( realDamage <= 0 ) return 0;
212 
213 	LOG( 5 ) INFO( "Avatar::applyDamage: player '%s' was hit\n",
214 	               static_cast<char*>( getPlayer()->getName() ));
215 	Uint8 r, g, b;
216 	real brightness;
217 
218 	LOG( 5 ) INFO( "Avatar::applyDamage: damage: %i => new health: %i\n",
219 	                realDamage, m_health );
220 
221 	m_worldPointer->getScorekeeper()
222 		->playerDamagedPlayer( shooter->getPlayerID(),
223 		                       getPlayer()->getPlayerID(),
224 		                       realDamage,
225 		                       m_health <= 0 );
226 
227 	if( m_health > 0 ) {
228 		// play sound
229 		if( m_damageSampleTimeout == 0 ) {
230 			Audio::getInstance()->playSound( m_injuredSample[m_worldPointer->getRandom().getUint32() % N_INJURED_SAMPLES],
231 			                                 ROUND( m_pos.x ), ROUND( m_pos.y ) );
232 			m_damageSampleTimeout = 5;
233 		}
234 
235 		// generate blood
236 		for( int i = 0; i < 3; i++ ) {
237 			brightness = 0.7 + 0.3*m_worldPointer->getRandom().getNormedReal();
238 			r = ROUND( static_cast<Uint8>( m_Colors[SKIN_COLOR] >> 16 ) * brightness );
239 			g = ROUND( static_cast<Uint8>( m_Colors[SKIN_COLOR] >>  8 ) * brightness );
240 			b = ROUND( static_cast<Uint8>( m_Colors[SKIN_COLOR]       ) * brightness );
241 
242 			Vector vel = m_vel;
243 			vel += m_vel;
244 			real length = m_worldPointer->getRandom().getNormedReal();
245 			length *= 4.0;
246 			length += 3.0;
247 			vel += m_worldPointer->getRandom().getVector( length );
248 			m_worldPointer->getParticles()->addParticle( Particles::ParticleData( m_pos,
249 			     vel,
250 			     Particles::BOUNCE_EARTH | Particles::STAIN | Particles::REDUNDANT, 0,
251 			     0, 75, 1, (((r << 8) | g) << 8) | b ));
252 		}
253 	}
254 	else {
255 		// remove avatar stencil
256 		removeCollRect();
257 		setState( VISIBLE | WEAPON_VISIBLE | LIVING | SHOOTING | DIGGING, false );
258 
259 		// leave gun properly
260 		leaveCurrentGun();
261 
262 		// detach from everything
263 		detachFromAll();
264 
265 		// disable low-pass filter
266 		Audio::getInstance()->setLowPassFilter( -1.0 );
267 
268 		// play sound
269 		Audio::getInstance()->playSound( m_dieSample[m_worldPointer->getRandom().getUint32() % N_DIE_SAMPLES],
270 		                                 ROUND( m_pos.x ), ROUND( m_pos.y) );
271 
272 		// generate a lot of blood
273 		for( int i = 0; i < 1000; i++ ) {
274 			brightness = 0.7 + 0.3*m_worldPointer->getRandom().getNormedReal();
275 			r = ROUND( static_cast<Uint8>( m_Colors[SKIN_COLOR] >> 16 ) * brightness );
276 			g = ROUND( static_cast<Uint8>( m_Colors[SKIN_COLOR] >>  8 ) * brightness );
277 			b = ROUND( static_cast<Uint8>( m_Colors[SKIN_COLOR]       ) * brightness );
278 
279 			Vector vel = m_vel;
280 			real length = m_worldPointer->getRandom().getNormedReal();
281 			length *= 6.0;
282 			length += 2.0;
283 			vel += m_worldPointer->getRandom().getVector( length );
284 			m_worldPointer->getParticles()->addParticle( Particles::ParticleData( m_pos,
285 			     vel,
286 			     Particles::BOUNCE_EARTH | Particles::BOUNCE_OBJECT | Particles::STAIN | Particles::REDUNDANT, 0,
287 			     0, 75, 1, (((r << 8) | g) << 8) | b ));
288 		}
289 
290 		// remove avatar stencil
291 		removeCollRect();
292 		setState( VISIBLE | WEAPON_VISIBLE | LIVING | SHOOTING, false );
293 
294 		// reset the bonus pack for the "sleeping state" of the avatar
295 		m_bonusPack.resetForRespawn();
296 	}
297 
298 	return realDamage;
299 }
300 
301 /**********************************************************/
302 
updateSpriteSelection()303 void AvatarWorm::updateSpriteSelection()
304 {
305 	if( m_state & Avatar::MODE_GUN ) {
306 		// developing check
307 		DBG(3) ASSERT(    FIRST_STATIONARY_GUN <= m_gunID
308 		               && LAST_STATIONARY_GUN  >= m_gunID,
309 		               "AvatarWorm::updateSpriteSelection: %d is not a "
310 		               "valid gun ID in range [%d,%d]\n", m_gunID,
311 		               FIRST_STATIONARY_GUN, LAST_STATIONARY_GUN );
312 		// set sequence
313 		setSequence( m_gunID + (FIRST_GUN_SEQUENCE - FIRST_STATIONARY_GUN) );
314 		setFrame( 0 );
315 	} else if( m_state & Avatar::MODE_GM ) {
316 		setSequence( FIRST_RC_SEQUENCE + getDirection() );
317 		setFrame( 0 );
318 	} else {
319 		// set the sequence index.
320 		// NOTE: setSequence also updates m_SequenceLength.
321 		setSequence( NUM_SEQUENCES_PER_DIRECTION * getDirection() +
322 		             (ROUND( getAimingAngle()) / AIMING_ANGLE_STEP) );
323 
324 		// only reset the frame index, if we cannot continue
325 		// this movement in a different aiming angle
326 		if( getFrame() >= m_SequenceLength )  setFrame( 0 );
327 	}
328 }
329 
330 /**********************************************************/
331 
~AvatarWorm()332 AvatarWorm::~AvatarWorm()
333 {
334 }
335 
336 /**********************************************************/
337