1 /*
2 ===========================================================================
3 
4 Doom 3 GPL Source Code
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
6 
7 This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
8 
9 Doom 3 Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13 
14 Doom 3 Source Code is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 GNU General Public License for more details.
18 
19 You should have received a copy of the GNU General Public License
20 along with Doom 3 Source Code.  If not, see <http://www.gnu.org/licenses/>.
21 
22 In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code.  If not, please request a copy in writing from id Software at the address below.
23 
24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
25 
26 ===========================================================================
27 */
28 
29 #include "sys/platform.h"
30 #include "renderer/ModelManager.h"
31 
32 #include "Fx.h"
33 
34 #include "Moveable.h"
35 
36 /*
37 ===============================================================================
38 
39   idMoveable
40 
41 ===============================================================================
42 */
43 
44 const idEventDef EV_BecomeNonSolid( "becomeNonSolid" );
45 const idEventDef EV_SetOwnerFromSpawnArgs( "<setOwnerFromSpawnArgs>" );
46 const idEventDef EV_IsAtRest( "isAtRest", NULL, 'd' );
47 const idEventDef EV_EnableDamage( "enableDamage", "f" );
48 
49 CLASS_DECLARATION( idEntity, idMoveable )
50 	EVENT( EV_Activate,					idMoveable::Event_Activate )
51 	EVENT( EV_BecomeNonSolid,			idMoveable::Event_BecomeNonSolid )
52 	EVENT( EV_SetOwnerFromSpawnArgs,	idMoveable::Event_SetOwnerFromSpawnArgs )
53 	EVENT( EV_IsAtRest,					idMoveable::Event_IsAtRest )
54 	EVENT( EV_EnableDamage,				idMoveable::Event_EnableDamage )
55 END_CLASS
56 
57 
58 static const float BOUNCE_SOUND_MIN_VELOCITY	= 80.0f;
59 static const float BOUNCE_SOUND_MAX_VELOCITY	= 200.0f;
60 
61 /*
62 ================
63 idMoveable::idMoveable
64 ================
65 */
idMoveable(void)66 idMoveable::idMoveable( void ) {
67 	minDamageVelocity	= 100.0f;
68 	maxDamageVelocity	= 200.0f;
69 	nextCollideFxTime	= 0;
70 	nextDamageTime		= 0;
71 	nextSoundTime		= 0;
72 	initialSpline		= NULL;
73 	initialSplineDir	= vec3_zero;
74 	explode				= false;
75 	unbindOnDeath		= false;
76 	allowStep			= false;
77 	canDamage			= false;
78 }
79 
80 /*
81 ================
82 idMoveable::~idMoveable
83 ================
84 */
~idMoveable(void)85 idMoveable::~idMoveable( void ) {
86 	delete initialSpline;
87 	initialSpline = NULL;
88 }
89 
90 /*
91 ================
92 idMoveable::Spawn
93 ================
94 */
Spawn(void)95 void idMoveable::Spawn( void ) {
96 	idTraceModel trm;
97 	float density, friction, bouncyness, mass;
98 	int clipShrink;
99 	idStr clipModelName;
100 
101 	// check if a clip model is set
102 	spawnArgs.GetString( "clipmodel", "", clipModelName );
103 	if ( !clipModelName[0] ) {
104 		clipModelName = spawnArgs.GetString( "model" );		// use the visual model
105 	}
106 
107 	if ( !collisionModelManager->TrmFromModel( clipModelName, trm ) ) {
108 		gameLocal.Error( "idMoveable '%s': cannot load collision model %s", name.c_str(), clipModelName.c_str() );
109 		return;
110 	}
111 
112 	// if the model should be shrinked
113 	clipShrink = spawnArgs.GetInt( "clipshrink" );
114 	if ( clipShrink != 0 ) {
115 		trm.Shrink( clipShrink * CM_CLIP_EPSILON );
116 	}
117 
118 	// get rigid body properties
119 	spawnArgs.GetFloat( "density", "0.5", density );
120 	density = idMath::ClampFloat( 0.001f, 1000.0f, density );
121 	spawnArgs.GetFloat( "friction", "0.05", friction );
122 	friction = idMath::ClampFloat( 0.0f, 1.0f, friction );
123 	spawnArgs.GetFloat( "bouncyness", "0.6", bouncyness );
124 	bouncyness = idMath::ClampFloat( 0.0f, 1.0f, bouncyness );
125 	explode = spawnArgs.GetBool( "explode" );
126 	unbindOnDeath = spawnArgs.GetBool( "unbindondeath" );
127 
128 	fxCollide = spawnArgs.GetString( "fx_collide" );
129 	nextCollideFxTime = 0;
130 
131 	fl.takedamage = true;
132 	damage = spawnArgs.GetString( "def_damage", "" );
133 	canDamage = spawnArgs.GetBool( "damageWhenActive" ) ? false : true;
134 	minDamageVelocity = spawnArgs.GetFloat( "minDamageVelocity", "100" );
135 	maxDamageVelocity = spawnArgs.GetFloat( "maxDamageVelocity", "200" );
136 	nextDamageTime = 0;
137 	nextSoundTime = 0;
138 
139 	health = spawnArgs.GetInt( "health", "0" );
140 	spawnArgs.GetString( "broken", "", brokenModel );
141 
142 	if ( health ) {
143 		if ( brokenModel != "" && !renderModelManager->CheckModel( brokenModel ) ) {
144 			gameLocal.Error( "idMoveable '%s' at (%s): cannot load broken model '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), brokenModel.c_str() );
145 		}
146 	}
147 
148 	// setup the physics
149 	physicsObj.SetSelf( this );
150 	physicsObj.SetClipModel( new idClipModel( trm ), density );
151 	physicsObj.GetClipModel()->SetMaterial( GetRenderModelMaterial() );
152 	physicsObj.SetOrigin( GetPhysics()->GetOrigin() );
153 	physicsObj.SetAxis( GetPhysics()->GetAxis() );
154 	physicsObj.SetBouncyness( bouncyness );
155 	physicsObj.SetFriction( 0.6f, 0.6f, friction );
156 	physicsObj.SetGravity( gameLocal.GetGravity() );
157 	physicsObj.SetContents( CONTENTS_SOLID );
158 	physicsObj.SetClipMask( MASK_SOLID | CONTENTS_BODY | CONTENTS_CORPSE | CONTENTS_MOVEABLECLIP );
159 	SetPhysics( &physicsObj );
160 
161 	if ( spawnArgs.GetFloat( "mass", "10", mass ) ) {
162 		physicsObj.SetMass( mass );
163 	}
164 
165 	if ( spawnArgs.GetBool( "nodrop" ) ) {
166 		physicsObj.PutToRest();
167 	} else {
168 		physicsObj.DropToFloor();
169 	}
170 
171 	if ( spawnArgs.GetBool( "noimpact" ) || spawnArgs.GetBool( "notPushable" ) ) {
172 		physicsObj.DisableImpact();
173 	}
174 
175 	if ( spawnArgs.GetBool( "nonsolid" ) ) {
176 		BecomeNonSolid();
177 	}
178 
179 	allowStep = spawnArgs.GetBool( "allowStep", "1" );
180 
181 	PostEventMS( &EV_SetOwnerFromSpawnArgs, 0 );
182 }
183 
184 /*
185 ================
186 idMoveable::Save
187 ================
188 */
Save(idSaveGame * savefile) const189 void idMoveable::Save( idSaveGame *savefile ) const {
190 
191 	savefile->WriteString( brokenModel );
192 	savefile->WriteString( damage );
193 	savefile->WriteString( fxCollide );
194 	savefile->WriteInt( nextCollideFxTime );
195 	savefile->WriteFloat( minDamageVelocity );
196 	savefile->WriteFloat( maxDamageVelocity );
197 	savefile->WriteBool( explode );
198 	savefile->WriteBool( unbindOnDeath );
199 	savefile->WriteBool( allowStep );
200 	savefile->WriteBool( canDamage );
201 	savefile->WriteInt( nextDamageTime );
202 	savefile->WriteInt( nextSoundTime );
203 	savefile->WriteInt( initialSpline != NULL ? initialSpline->GetTime( 0 ) : -1 );
204 	savefile->WriteVec3( initialSplineDir );
205 
206 	savefile->WriteStaticObject( physicsObj );
207 }
208 
209 /*
210 ================
211 idMoveable::Restore
212 ================
213 */
Restore(idRestoreGame * savefile)214 void idMoveable::Restore( idRestoreGame *savefile ) {
215 	int initialSplineTime;
216 
217 	savefile->ReadString( brokenModel );
218 	savefile->ReadString( damage );
219 	savefile->ReadString( fxCollide );
220 	savefile->ReadInt( nextCollideFxTime );
221 	savefile->ReadFloat( minDamageVelocity );
222 	savefile->ReadFloat( maxDamageVelocity );
223 	savefile->ReadBool( explode );
224 	savefile->ReadBool( unbindOnDeath );
225 	savefile->ReadBool( allowStep );
226 	savefile->ReadBool( canDamage );
227 	savefile->ReadInt( nextDamageTime );
228 	savefile->ReadInt( nextSoundTime );
229 	savefile->ReadInt( initialSplineTime );
230 	savefile->ReadVec3( initialSplineDir );
231 
232 	if ( initialSplineTime != -1 ) {
233 		InitInitialSpline( initialSplineTime );
234 	} else {
235 		initialSpline = NULL;
236 	}
237 
238 	savefile->ReadStaticObject( physicsObj );
239 	RestorePhysics( &physicsObj );
240 }
241 
242 /*
243 ================
244 idMoveable::Hide
245 ================
246 */
Hide(void)247 void idMoveable::Hide( void ) {
248 	idEntity::Hide();
249 	physicsObj.SetContents( 0 );
250 }
251 
252 /*
253 ================
254 idMoveable::Show
255 ================
256 */
Show(void)257 void idMoveable::Show( void ) {
258 	idEntity::Show();
259 	if ( !spawnArgs.GetBool( "nonsolid" ) ) {
260 		physicsObj.SetContents( CONTENTS_SOLID );
261 	}
262 }
263 
264 /*
265 =================
266 idMoveable::Collide
267 =================
268 */
Collide(const trace_t & collision,const idVec3 & velocity)269 bool idMoveable::Collide( const trace_t &collision, const idVec3 &velocity ) {
270 	float v, f;
271 	idVec3 dir;
272 	idEntity *ent;
273 
274 	v = -( velocity * collision.c.normal );
275 	if ( v > BOUNCE_SOUND_MIN_VELOCITY && gameLocal.time > nextSoundTime ) {
276 		f = v > BOUNCE_SOUND_MAX_VELOCITY ? 1.0f : idMath::Sqrt( v - BOUNCE_SOUND_MIN_VELOCITY ) * ( 1.0f / idMath::Sqrt( BOUNCE_SOUND_MAX_VELOCITY - BOUNCE_SOUND_MIN_VELOCITY ) );
277 		if ( StartSound( "snd_bounce", SND_CHANNEL_ANY, 0, false, NULL ) ) {
278 			// don't set the volume unless there is a bounce sound as it overrides the entire channel
279 			// which causes footsteps on ai's to not honor their shader parms
280 			SetSoundVolume( f );
281 		}
282 		nextSoundTime = gameLocal.time + 500;
283 	}
284 
285 	if ( canDamage && damage.Length() && gameLocal.time > nextDamageTime ) {
286 		ent = gameLocal.entities[ collision.c.entityNum ];
287 		if ( ent && v > minDamageVelocity ) {
288 			f = v > maxDamageVelocity ? 1.0f : idMath::Sqrt( v - minDamageVelocity ) * ( 1.0f / idMath::Sqrt( maxDamageVelocity - minDamageVelocity ) );
289 			dir = velocity;
290 			dir.NormalizeFast();
291 			ent->Damage( this, GetPhysics()->GetClipModel()->GetOwner(), dir, damage, f, INVALID_JOINT );
292 			nextDamageTime = gameLocal.time + 1000;
293 		}
294 	}
295 
296 	if ( fxCollide.Length() && gameLocal.time > nextCollideFxTime ) {
297 		idEntityFx::StartFx( fxCollide, &collision.c.point, NULL, this, false );
298 		nextCollideFxTime = gameLocal.time + 3500;
299 	}
300 
301 	return false;
302 }
303 
304 /*
305 ============
306 idMoveable::Killed
307 ============
308 */
Killed(idEntity * inflictor,idEntity * attacker,int damage,const idVec3 & dir,int location)309 void idMoveable::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
310 	if ( unbindOnDeath ) {
311 		Unbind();
312 	}
313 
314 	if ( brokenModel != "" ) {
315 		SetModel( brokenModel );
316 	}
317 
318 	if ( explode ) {
319 		if ( brokenModel == "" ) {
320 			PostEventMS( &EV_Remove, 1000 );
321 		}
322 	}
323 
324 	if ( renderEntity.gui[ 0 ] ) {
325 		renderEntity.gui[ 0 ] = NULL;
326 	}
327 
328 	ActivateTargets( this );
329 
330 	fl.takedamage = false;
331 }
332 
333 /*
334 ================
335 idMoveable::AllowStep
336 ================
337 */
AllowStep(void) const338 bool idMoveable::AllowStep( void ) const {
339 	return allowStep;
340 }
341 
342 /*
343 ================
344 idMoveable::BecomeNonSolid
345 ================
346 */
BecomeNonSolid(void)347 void idMoveable::BecomeNonSolid( void ) {
348 	// set CONTENTS_RENDERMODEL so bullets still collide with the moveable
349 	physicsObj.SetContents( CONTENTS_CORPSE | CONTENTS_RENDERMODEL );
350 	physicsObj.SetClipMask( MASK_SOLID | CONTENTS_CORPSE | CONTENTS_MOVEABLECLIP );
351 }
352 
353 /*
354 ================
355 idMoveable::EnableDamage
356 ================
357 */
EnableDamage(bool enable,float duration)358 void idMoveable::EnableDamage( bool enable, float duration ) {
359 	canDamage = enable;
360 	if ( duration ) {
361 		PostEventSec( &EV_EnableDamage, duration, ( !enable ) ? 0.0f : 1.0f );
362 	}
363 }
364 
365 /*
366 ================
367 idMoveable::InitInitialSpline
368 ================
369 */
InitInitialSpline(int startTime)370 void idMoveable::InitInitialSpline( int startTime ) {
371 	int initialSplineTime;
372 
373 	initialSpline = GetSpline();
374 	initialSplineTime = spawnArgs.GetInt( "initialSplineTime", "300" );
375 
376 	if ( initialSpline != NULL ) {
377 		initialSpline->MakeUniform( initialSplineTime );
378 		initialSpline->ShiftTime( startTime - initialSpline->GetTime( 0 ) );
379 		initialSplineDir = initialSpline->GetCurrentFirstDerivative( startTime );
380 		initialSplineDir *= physicsObj.GetAxis().Transpose();
381 		initialSplineDir.Normalize();
382 		BecomeActive( TH_THINK );
383 	}
384 }
385 
386 /*
387 ================
388 idMoveable::FollowInitialSplinePath
389 ================
390 */
FollowInitialSplinePath(void)391 bool idMoveable::FollowInitialSplinePath( void ) {
392 	if ( initialSpline != NULL ) {
393 		if ( gameLocal.time < initialSpline->GetTime( initialSpline->GetNumValues() - 1 ) ) {
394 			idVec3 splinePos = initialSpline->GetCurrentValue( gameLocal.time );
395 			idVec3 linearVelocity = ( splinePos - physicsObj.GetOrigin() ) * USERCMD_HZ;
396 			physicsObj.SetLinearVelocity( linearVelocity );
397 
398 			idVec3 splineDir = initialSpline->GetCurrentFirstDerivative( gameLocal.time );
399 			idVec3 dir = initialSplineDir * physicsObj.GetAxis();
400 			idVec3 angularVelocity = dir.Cross( splineDir );
401 			angularVelocity.Normalize();
402 			angularVelocity *= idMath::ACos16( dir * splineDir / splineDir.Length() ) * USERCMD_HZ;
403 			physicsObj.SetAngularVelocity( angularVelocity );
404 			return true;
405 		} else {
406 			delete initialSpline;
407 			initialSpline = NULL;
408 		}
409 	}
410 	return false;
411 }
412 
413 /*
414 ================
415 idMoveable::Think
416 ================
417 */
Think(void)418 void idMoveable::Think( void ) {
419 	if ( thinkFlags & TH_THINK ) {
420 		if ( !FollowInitialSplinePath() ) {
421 			BecomeInactive( TH_THINK );
422 		}
423 	}
424 	idEntity::Think();
425 }
426 
427 /*
428 ================
429 idMoveable::GetRenderModelMaterial
430 ================
431 */
GetRenderModelMaterial(void) const432 const idMaterial *idMoveable::GetRenderModelMaterial( void ) const {
433 	if ( renderEntity.customShader ) {
434 		return renderEntity.customShader;
435 	}
436 	if ( renderEntity.hModel && renderEntity.hModel->NumSurfaces() ) {
437 		 return renderEntity.hModel->Surface( 0 )->shader;
438 	}
439 	return NULL;
440 }
441 
442 /*
443 ================
444 idMoveable::WriteToSnapshot
445 ================
446 */
WriteToSnapshot(idBitMsgDelta & msg) const447 void idMoveable::WriteToSnapshot( idBitMsgDelta &msg ) const {
448 	physicsObj.WriteToSnapshot( msg );
449 }
450 
451 /*
452 ================
453 idMoveable::ReadFromSnapshot
454 ================
455 */
ReadFromSnapshot(const idBitMsgDelta & msg)456 void idMoveable::ReadFromSnapshot( const idBitMsgDelta &msg ) {
457 	physicsObj.ReadFromSnapshot( msg );
458 	if ( msg.HasChanged() ) {
459 		UpdateVisuals();
460 	}
461 }
462 
463 /*
464 ================
465 idMoveable::Event_BecomeNonSolid
466 ================
467 */
Event_BecomeNonSolid(void)468 void idMoveable::Event_BecomeNonSolid( void ) {
469 	BecomeNonSolid();
470 }
471 
472 /*
473 ================
474 idMoveable::Event_Activate
475 ================
476 */
Event_Activate(idEntity * activator)477 void idMoveable::Event_Activate( idEntity *activator ) {
478 	float delay;
479 	idVec3 init_velocity, init_avelocity;
480 
481 	Show();
482 
483 	if ( !spawnArgs.GetInt( "notPushable" ) ) {
484 		physicsObj.EnableImpact();
485 	}
486 
487 	physicsObj.Activate();
488 
489 	spawnArgs.GetVector( "init_velocity", "0 0 0", init_velocity );
490 	spawnArgs.GetVector( "init_avelocity", "0 0 0", init_avelocity );
491 
492 	delay = spawnArgs.GetFloat( "init_velocityDelay", "0" );
493 	if ( delay == 0.0f ) {
494 		physicsObj.SetLinearVelocity( init_velocity );
495 	} else {
496 		PostEventSec( &EV_SetLinearVelocity, delay, init_velocity );
497 	}
498 
499 	delay = spawnArgs.GetFloat( "init_avelocityDelay", "0" );
500 	if ( delay == 0.0f ) {
501 		physicsObj.SetAngularVelocity( init_avelocity );
502 	} else {
503 		PostEventSec( &EV_SetAngularVelocity, delay, init_avelocity );
504 	}
505 
506 	InitInitialSpline( gameLocal.time );
507 }
508 
509 /*
510 ================
511 idMoveable::Event_SetOwnerFromSpawnArgs
512 ================
513 */
Event_SetOwnerFromSpawnArgs(void)514 void idMoveable::Event_SetOwnerFromSpawnArgs( void ) {
515 	idStr owner;
516 
517 	if ( spawnArgs.GetString( "owner", "", owner ) ) {
518 		ProcessEvent( &EV_SetOwner, gameLocal.FindEntity( owner ) );
519 	}
520 }
521 
522 /*
523 ================
524 idMoveable::Event_IsAtRest
525 ================
526 */
Event_IsAtRest(void)527 void idMoveable::Event_IsAtRest( void ) {
528 	idThread::ReturnInt( physicsObj.IsAtRest() );
529 }
530 
531 /*
532 ================
533 idMoveable::Event_EnableDamage
534 ================
535 */
Event_EnableDamage(float enable)536 void idMoveable::Event_EnableDamage( float enable ) {
537 	canDamage = ( enable != 0.0f );
538 }
539 
540 
541 /*
542 ===============================================================================
543 
544   idBarrel
545 
546 ===============================================================================
547 */
548 
CLASS_DECLARATION(idMoveable,idBarrel)549 CLASS_DECLARATION( idMoveable, idBarrel )
550 END_CLASS
551 
552 /*
553 ================
554 idBarrel::idBarrel
555 ================
556 */
557 idBarrel::idBarrel() {
558 	radius = 1.0f;
559 	barrelAxis = 0;
560 	lastOrigin.Zero();
561 	lastAxis.Identity();
562 	additionalRotation = 0.0f;
563 	additionalAxis.Identity();
564 	fl.networkSync = true;
565 }
566 
567 /*
568 ================
569 idBarrel::Save
570 ================
571 */
Save(idSaveGame * savefile) const572 void idBarrel::Save( idSaveGame *savefile ) const {
573 	savefile->WriteFloat( radius );
574 	savefile->WriteInt( barrelAxis );
575 	savefile->WriteVec3( lastOrigin );
576 	savefile->WriteMat3( lastAxis );
577 	savefile->WriteFloat( additionalRotation );
578 	savefile->WriteMat3( additionalAxis );
579 }
580 
581 /*
582 ================
583 idBarrel::Restore
584 ================
585 */
Restore(idRestoreGame * savefile)586 void idBarrel::Restore( idRestoreGame *savefile ) {
587 	savefile->ReadFloat( radius );
588 	savefile->ReadInt( barrelAxis );
589 	savefile->ReadVec3( lastOrigin );
590 	savefile->ReadMat3( lastAxis );
591 	savefile->ReadFloat( additionalRotation );
592 	savefile->ReadMat3( additionalAxis );
593 }
594 
595 /*
596 ================
597 idBarrel::BarrelThink
598 ================
599 */
BarrelThink(void)600 void idBarrel::BarrelThink( void ) {
601 	bool wasAtRest, onGround;
602 	float movedDistance, rotatedDistance, angle;
603 	idVec3 curOrigin, gravityNormal, dir;
604 	idMat3 curAxis, axis;
605 
606 	wasAtRest = IsAtRest();
607 
608 	// run physics
609 	RunPhysics();
610 
611 	// only need to give the visual model an additional rotation if the physics were run
612 	if ( !wasAtRest ) {
613 
614 		// current physics state
615 		onGround = GetPhysics()->HasGroundContacts();
616 		curOrigin = GetPhysics()->GetOrigin();
617 		curAxis = GetPhysics()->GetAxis();
618 
619 		// if the barrel is on the ground
620 		if ( onGround ) {
621 			gravityNormal = GetPhysics()->GetGravityNormal();
622 
623 			dir = curOrigin - lastOrigin;
624 			dir -= gravityNormal * dir * gravityNormal;
625 			movedDistance = dir.LengthSqr();
626 
627 			// if the barrel moved and the barrel is not aligned with the gravity direction
628 			if ( movedDistance > 0.0f && idMath::Fabs( gravityNormal * curAxis[barrelAxis] ) < 0.7f ) {
629 
630 				// barrel movement since last think frame orthogonal to the barrel axis
631 				movedDistance = idMath::Sqrt( movedDistance );
632 				dir *= 1.0f / movedDistance;
633 				movedDistance = ( 1.0f - idMath::Fabs( dir * curAxis[barrelAxis] ) ) * movedDistance;
634 
635 				// get rotation about barrel axis since last think frame
636 				angle = lastAxis[(barrelAxis+1)%3] * curAxis[(barrelAxis+1)%3];
637 				angle = idMath::ACos( angle );
638 				// distance along cylinder hull
639 				rotatedDistance = angle * radius;
640 
641 				// if the barrel moved further than it rotated about it's axis
642 				if ( movedDistance > rotatedDistance ) {
643 
644 					// additional rotation of the visual model to make it look
645 					// like the barrel rolls instead of slides
646 					angle = 180.0f * (movedDistance - rotatedDistance) / (radius * idMath::PI);
647 					if ( gravityNormal.Cross( curAxis[barrelAxis] ) * dir < 0.0f ) {
648 						additionalRotation += angle;
649 					} else {
650 						additionalRotation -= angle;
651 					}
652 					dir = vec3_origin;
653 					dir[barrelAxis] = 1.0f;
654 					additionalAxis = idRotation( vec3_origin, dir, additionalRotation ).ToMat3();
655 				}
656 			}
657 		}
658 
659 		// save state for next think
660 		lastOrigin = curOrigin;
661 		lastAxis = curAxis;
662 	}
663 
664 	Present();
665 }
666 
667 /*
668 ================
669 idBarrel::Think
670 ================
671 */
Think(void)672 void idBarrel::Think( void ) {
673 	if ( thinkFlags & TH_THINK ) {
674 		if ( !FollowInitialSplinePath() ) {
675 			BecomeInactive( TH_THINK );
676 		}
677 	}
678 
679 	BarrelThink();
680 }
681 
682 /*
683 ================
684 idBarrel::GetPhysicsToVisualTransform
685 ================
686 */
GetPhysicsToVisualTransform(idVec3 & origin,idMat3 & axis)687 bool idBarrel::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) {
688 	origin = vec3_origin;
689 	axis = additionalAxis;
690 	return true;
691 }
692 
693 /*
694 ================
695 idBarrel::Spawn
696 ================
697 */
Spawn(void)698 void idBarrel::Spawn( void ) {
699 	const idBounds &bounds = GetPhysics()->GetBounds();
700 
701 	// radius of the barrel cylinder
702 	radius = ( bounds[1][0] - bounds[0][0] ) * 0.5f;
703 
704 	// always a vertical barrel with cylinder axis parallel to the z-axis
705 	barrelAxis = 2;
706 
707 	lastOrigin = GetPhysics()->GetOrigin();
708 	lastAxis = GetPhysics()->GetAxis();
709 
710 	additionalRotation = 0.0f;
711 	additionalAxis.Identity();
712 }
713 
714 /*
715 ================
716 idBarrel::ClientPredictionThink
717 ================
718 */
ClientPredictionThink(void)719 void idBarrel::ClientPredictionThink( void ) {
720 	Think();
721 }
722 
723 
724 /*
725 ===============================================================================
726 
727 idExplodingBarrel
728 
729 ===============================================================================
730 */
731 const idEventDef EV_Respawn( "<respawn>" );
732 const idEventDef EV_TriggerTargets( "<triggertargets>" );
733 
CLASS_DECLARATION(idBarrel,idExplodingBarrel)734 CLASS_DECLARATION( idBarrel, idExplodingBarrel )
735 	EVENT( EV_Activate,					idExplodingBarrel::Event_Activate )
736 	EVENT( EV_Respawn,					idExplodingBarrel::Event_Respawn )
737 	EVENT( EV_Explode,					idExplodingBarrel::Event_Explode )
738 	EVENT( EV_TriggerTargets,			idExplodingBarrel::Event_TriggerTargets )
739 END_CLASS
740 
741 /*
742 ================
743 idExplodingBarrel::idExplodingBarrel
744 ================
745 */
746 idExplodingBarrel::idExplodingBarrel() {
747 	spawnOrigin.Zero();
748 	spawnAxis.Zero();
749 	state = NORMAL;
750 	particleModelDefHandle = -1;
751 	lightDefHandle = -1;
752 	memset( &particleRenderEntity, 0, sizeof( particleRenderEntity ) );
753 	memset( &light, 0, sizeof( light ) );
754 	particleTime = 0;
755 	lightTime = 0;
756 	time = 0.0f;
757 }
758 
759 /*
760 ================
761 idExplodingBarrel::~idExplodingBarrel
762 ================
763 */
~idExplodingBarrel()764 idExplodingBarrel::~idExplodingBarrel() {
765 	if ( particleModelDefHandle >= 0 ){
766 		gameRenderWorld->FreeEntityDef( particleModelDefHandle );
767 	}
768 	if ( lightDefHandle >= 0 ) {
769 		gameRenderWorld->FreeLightDef( lightDefHandle );
770 	}
771 }
772 
773 /*
774 ================
775 idExplodingBarrel::Save
776 ================
777 */
Save(idSaveGame * savefile) const778 void idExplodingBarrel::Save( idSaveGame *savefile ) const {
779 	savefile->WriteVec3( spawnOrigin );
780 	savefile->WriteMat3( spawnAxis );
781 
782 	savefile->WriteInt( state );
783 	savefile->WriteInt( particleModelDefHandle );
784 	savefile->WriteInt( lightDefHandle );
785 
786 	savefile->WriteRenderEntity( particleRenderEntity );
787 	savefile->WriteRenderLight( light );
788 
789 	savefile->WriteInt( particleTime );
790 	savefile->WriteInt( lightTime );
791 	savefile->WriteFloat( time );
792 }
793 
794 /*
795 ================
796 idExplodingBarrel::Restore
797 ================
798 */
Restore(idRestoreGame * savefile)799 void idExplodingBarrel::Restore( idRestoreGame *savefile ) {
800 	savefile->ReadVec3( spawnOrigin );
801 	savefile->ReadMat3( spawnAxis );
802 
803 	savefile->ReadInt( (int &)state );
804 	savefile->ReadInt( (int &)particleModelDefHandle );
805 	savefile->ReadInt( (int &)lightDefHandle );
806 
807 	savefile->ReadRenderEntity( particleRenderEntity );
808 	savefile->ReadRenderLight( light );
809 
810 	savefile->ReadInt( particleTime );
811 	savefile->ReadInt( lightTime );
812 	savefile->ReadFloat( time );
813 }
814 
815 /*
816 ================
817 idExplodingBarrel::Spawn
818 ================
819 */
Spawn(void)820 void idExplodingBarrel::Spawn( void ) {
821 	health = spawnArgs.GetInt( "health", "5" );
822 	fl.takedamage = true;
823 	spawnOrigin = GetPhysics()->GetOrigin();
824 	spawnAxis = GetPhysics()->GetAxis();
825 	state = NORMAL;
826 	particleModelDefHandle = -1;
827 	lightDefHandle = -1;
828 	lightTime = 0;
829 	particleTime = 0;
830 	time = spawnArgs.GetFloat( "time" );
831 	memset( &particleRenderEntity, 0, sizeof( particleRenderEntity ) );
832 	memset( &light, 0, sizeof( light ) );
833 }
834 
835 /*
836 ================
837 idExplodingBarrel::Think
838 ================
839 */
Think(void)840 void idExplodingBarrel::Think( void ) {
841 	idBarrel::BarrelThink();
842 
843 	if ( lightDefHandle >= 0 ){
844 		if ( state == BURNING ) {
845 			// ramp the color up over 250 ms
846 			float pct = (gameLocal.time - lightTime) / 250.f;
847 			if ( pct > 1.0f ) {
848 				pct = 1.0f;
849 			}
850 			light.origin = physicsObj.GetAbsBounds().GetCenter();
851 			light.axis = mat3_identity;
852 			light.shaderParms[ SHADERPARM_RED ] = pct;
853 			light.shaderParms[ SHADERPARM_GREEN ] = pct;
854 			light.shaderParms[ SHADERPARM_BLUE ] = pct;
855 			light.shaderParms[ SHADERPARM_ALPHA ] = pct;
856 			gameRenderWorld->UpdateLightDef( lightDefHandle, &light );
857 		} else {
858 			if ( gameLocal.time - lightTime > 250 ) {
859 				gameRenderWorld->FreeLightDef( lightDefHandle );
860 				lightDefHandle = -1;
861 			}
862 			return;
863 		}
864 	}
865 
866 	if ( !gameLocal.isClient && state != BURNING && state != EXPLODING ) {
867 		BecomeInactive( TH_THINK );
868 		return;
869 	}
870 
871 	if ( particleModelDefHandle >= 0 ){
872 		particleRenderEntity.origin = physicsObj.GetAbsBounds().GetCenter();
873 		particleRenderEntity.axis = mat3_identity;
874 		gameRenderWorld->UpdateEntityDef( particleModelDefHandle, &particleRenderEntity );
875 	}
876 }
877 
878 /*
879 ================
880 idExplodingBarrel::AddParticles
881 ================
882 */
AddParticles(const char * name,bool burn)883 void idExplodingBarrel::AddParticles( const char *name, bool burn ) {
884 	if ( name && *name ) {
885 		if ( particleModelDefHandle >= 0 ){
886 			gameRenderWorld->FreeEntityDef( particleModelDefHandle );
887 		}
888 		memset( &particleRenderEntity, 0, sizeof ( particleRenderEntity ) );
889 		const idDeclModelDef *modelDef = static_cast<const idDeclModelDef *>( declManager->FindType( DECL_MODELDEF, name ) );
890 		if ( modelDef ) {
891 			particleRenderEntity.origin = physicsObj.GetAbsBounds().GetCenter();
892 			particleRenderEntity.axis = mat3_identity;
893 			particleRenderEntity.hModel = modelDef->ModelHandle();
894 			float rgb = ( burn ) ? 0.0f : 1.0f;
895 			particleRenderEntity.shaderParms[ SHADERPARM_RED ] = rgb;
896 			particleRenderEntity.shaderParms[ SHADERPARM_GREEN ] = rgb;
897 			particleRenderEntity.shaderParms[ SHADERPARM_BLUE ] = rgb;
898 			particleRenderEntity.shaderParms[ SHADERPARM_ALPHA ] = rgb;
899 			particleRenderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.realClientTime );
900 			particleRenderEntity.shaderParms[ SHADERPARM_DIVERSITY ] = ( burn ) ? 1.0f : gameLocal.random.RandomInt( 90 );
901 			if ( !particleRenderEntity.hModel ) {
902 				particleRenderEntity.hModel = renderModelManager->FindModel( name );
903 			}
904 			particleModelDefHandle = gameRenderWorld->AddEntityDef( &particleRenderEntity );
905 			if ( burn ) {
906 				BecomeActive( TH_THINK );
907 			}
908 			particleTime = gameLocal.realClientTime;
909 		}
910 	}
911 }
912 
913 /*
914 ================
915 idExplodingBarrel::AddLight
916 ================
917 */
AddLight(const char * name,bool burn)918 void idExplodingBarrel::AddLight( const char *name, bool burn ) {
919 	if ( lightDefHandle >= 0 ){
920 		gameRenderWorld->FreeLightDef( lightDefHandle );
921 	}
922 	memset( &light, 0, sizeof ( light ) );
923 	light.axis = mat3_identity;
924 	light.lightRadius.x = spawnArgs.GetFloat( "light_radius" );
925 	light.lightRadius.y = light.lightRadius.z = light.lightRadius.x;
926 	light.origin = physicsObj.GetOrigin();
927 	light.origin.z += 128;
928 	light.pointLight = true;
929 	light.shader = declManager->FindMaterial( name );
930 	light.shaderParms[ SHADERPARM_RED ] = 2.0f;
931 	light.shaderParms[ SHADERPARM_GREEN ] = 2.0f;
932 	light.shaderParms[ SHADERPARM_BLUE ] = 2.0f;
933 	light.shaderParms[ SHADERPARM_ALPHA ] = 2.0f;
934 	lightDefHandle = gameRenderWorld->AddLightDef( &light );
935 	lightTime = gameLocal.realClientTime;
936 	BecomeActive( TH_THINK );
937 }
938 
939 /*
940 ================
941 idExplodingBarrel::ExplodingEffects
942 ================
943 */
ExplodingEffects(void)944 void idExplodingBarrel::ExplodingEffects( void ) {
945 	const char *temp;
946 
947 	StartSound( "snd_explode", SND_CHANNEL_ANY, 0, false, NULL );
948 
949 	temp = spawnArgs.GetString( "model_damage" );
950 	if ( *temp != '\0' ) {
951 		SetModel( temp );
952 		Show();
953 	}
954 
955 	temp = spawnArgs.GetString( "model_detonate" );
956 	if ( *temp != '\0' ) {
957 		AddParticles( temp, false );
958 	}
959 
960 	temp = spawnArgs.GetString( "mtr_lightexplode" );
961 	if ( *temp != '\0' ) {
962 		AddLight( temp, false );
963 	}
964 
965 	temp = spawnArgs.GetString( "mtr_burnmark" );
966 	if ( *temp != '\0' ) {
967 		gameLocal.ProjectDecal( GetPhysics()->GetOrigin(), GetPhysics()->GetGravity(), 128.0f, true, 96.0f, temp );
968 	}
969 }
970 
971 /*
972 ================
973 idExplodingBarrel::Killed
974 ================
975 */
Killed(idEntity * inflictor,idEntity * attacker,int damage,const idVec3 & dir,int location)976 void idExplodingBarrel::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
977 
978 	if ( IsHidden() || state == EXPLODING || state == BURNING ) {
979 		return;
980 	}
981 
982 	float f = spawnArgs.GetFloat( "burn" );
983 	if ( f > 0.0f && state == NORMAL ) {
984 		state = BURNING;
985 		PostEventSec( &EV_Explode, f );
986 		StartSound( "snd_burn", SND_CHANNEL_ANY, 0, false, NULL );
987 		AddParticles( spawnArgs.GetString ( "model_burn", "" ), true );
988 		return;
989 	} else {
990 		state = EXPLODING;
991 		if ( gameLocal.isServer ) {
992 			idBitMsg	msg;
993 			byte		msgBuf[MAX_EVENT_PARAM_SIZE];
994 
995 			msg.Init( msgBuf, sizeof( msgBuf ) );
996 			msg.WriteInt( gameLocal.time );
997 			ServerSendEvent( EVENT_EXPLODE, &msg, false, -1 );
998 		}
999 	}
1000 
1001 	// do this before applying radius damage so the ent can trace to any damagable ents nearby
1002 	Hide();
1003 	physicsObj.SetContents( 0 );
1004 
1005 	const char *splash = spawnArgs.GetString( "def_splash_damage", "damage_explosion" );
1006 	if ( splash && *splash ) {
1007 		gameLocal.RadiusDamage( GetPhysics()->GetOrigin(), this, attacker, this, this, splash );
1008 	}
1009 
1010 	ExplodingEffects( );
1011 
1012 	//FIXME: need to precache all the debris stuff here and in the projectiles
1013 	const idKeyValue *kv = spawnArgs.MatchPrefix( "def_debris" );
1014 	// bool first = true;
1015 	while ( kv ) {
1016 		const idDict *debris_args = gameLocal.FindEntityDefDict( kv->GetValue(), false );
1017 		if ( debris_args ) {
1018 			idEntity *ent;
1019 			idVec3 dir;
1020 			idDebris *debris;
1021 			//if ( first ) {
1022 				dir = physicsObj.GetAxis()[1];
1023 			//	first = false;
1024 			//} else {
1025 				dir.x += gameLocal.random.CRandomFloat() * 4.0f;
1026 				dir.y += gameLocal.random.CRandomFloat() * 4.0f;
1027 				//dir.z = gameLocal.random.RandomFloat() * 8.0f;
1028 			//}
1029 			dir.Normalize();
1030 
1031 			gameLocal.SpawnEntityDef( *debris_args, &ent, false );
1032 			if ( !ent || !ent->IsType( idDebris::Type ) ) {
1033 				gameLocal.Error( "'projectile_debris' is not an idDebris" );
1034 			}
1035 
1036 			debris = static_cast<idDebris *>(ent);
1037 			debris->Create( this, physicsObj.GetOrigin(), dir.ToMat3() );
1038 			debris->Launch();
1039 			debris->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = ( gameLocal.time + 1500 ) * 0.001f;
1040 			debris->UpdateVisuals();
1041 
1042 		}
1043 		kv = spawnArgs.MatchPrefix( "def_debris", kv );
1044 	}
1045 
1046 	physicsObj.PutToRest();
1047 	CancelEvents( &EV_Explode );
1048 	CancelEvents( &EV_Activate );
1049 
1050 	f = spawnArgs.GetFloat( "respawn" );
1051 	if ( f > 0.0f ) {
1052 		PostEventSec( &EV_Respawn, f );
1053 	} else {
1054 		PostEventMS( &EV_Remove, 5000 );
1055 	}
1056 
1057 	if ( spawnArgs.GetBool( "triggerTargets" ) ) {
1058 		ActivateTargets( this );
1059 	}
1060 }
1061 
1062 /*
1063 ================
1064 idExplodingBarrel::Damage
1065 ================
1066 */
Damage(idEntity * inflictor,idEntity * attacker,const idVec3 & dir,const char * damageDefName,const float damageScale,const int location)1067 void idExplodingBarrel::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir,
1068 					  const char *damageDefName, const float damageScale, const int location ) {
1069 
1070 	const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName );
1071 	if ( !damageDef ) {
1072 		gameLocal.Error( "Unknown damageDef '%s'\n", damageDefName );
1073 	}
1074 	if ( damageDef->FindKey( "radius" ) && GetPhysics()->GetContents() != 0 && GetBindMaster() == NULL ) {
1075 		PostEventMS( &EV_Explode, 400 );
1076 	} else {
1077 		idEntity::Damage( inflictor, attacker, dir, damageDefName, damageScale, location );
1078 	}
1079 }
1080 
1081 /*
1082 ================
1083 idExplodingBarrel::Event_TriggerTargets
1084 ================
1085 */
Event_TriggerTargets()1086 void idExplodingBarrel::Event_TriggerTargets() {
1087 	ActivateTargets( this );
1088 }
1089 
1090 /*
1091 ================
1092 idExplodingBarrel::Event_Explode
1093 ================
1094 */
Event_Explode()1095 void idExplodingBarrel::Event_Explode() {
1096 	if ( state == NORMAL || state == BURNING ) {
1097 		state = BURNEXPIRED;
1098 		Killed( NULL, NULL, 0, vec3_zero, 0 );
1099 	}
1100 }
1101 
1102 /*
1103 ================
1104 idExplodingBarrel::Event_Respawn
1105 ================
1106 */
Event_Respawn()1107 void idExplodingBarrel::Event_Respawn() {
1108 	int i;
1109 	int minRespawnDist = spawnArgs.GetInt( "respawn_range", "256" );
1110 	if ( minRespawnDist ) {
1111 		float minDist = -1;
1112 		for ( i = 0; i < gameLocal.numClients; i++ ) {
1113 			if ( !gameLocal.entities[ i ] || !gameLocal.entities[ i ]->IsType( idPlayer::Type ) ) {
1114 				continue;
1115 			}
1116 			idVec3 v = gameLocal.entities[ i ]->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin();
1117 			float dist = v.Length();
1118 			if ( minDist < 0 || dist < minDist ) {
1119 				minDist = dist;
1120 			}
1121 		}
1122 		if ( minDist < minRespawnDist ) {
1123 			PostEventSec( &EV_Respawn, spawnArgs.GetInt( "respawn_again", "10" ) );
1124 			return;
1125 		}
1126 	}
1127 	const char *temp = spawnArgs.GetString( "model" );
1128 	if ( temp && *temp ) {
1129 		SetModel( temp );
1130 	}
1131 	health = spawnArgs.GetInt( "health", "5" );
1132 	fl.takedamage = true;
1133 	physicsObj.SetOrigin( spawnOrigin );
1134 	physicsObj.SetAxis( spawnAxis );
1135 	physicsObj.SetContents( CONTENTS_SOLID );
1136 	physicsObj.DropToFloor();
1137 	state = NORMAL;
1138 	Show();
1139 	UpdateVisuals();
1140 }
1141 
1142 /*
1143 ================
1144 idMoveable::Event_Activate
1145 ================
1146 */
Event_Activate(idEntity * activator)1147 void idExplodingBarrel::Event_Activate( idEntity *activator ) {
1148 	Killed( activator, activator, 0, vec3_origin, 0 );
1149 }
1150 
1151 /*
1152 ================
1153 idMoveable::WriteToSnapshot
1154 ================
1155 */
WriteToSnapshot(idBitMsgDelta & msg) const1156 void idExplodingBarrel::WriteToSnapshot( idBitMsgDelta &msg ) const {
1157 	idMoveable::WriteToSnapshot( msg );
1158 	msg.WriteBits( IsHidden(), 1 );
1159 }
1160 
1161 /*
1162 ================
1163 idMoveable::ReadFromSnapshot
1164 ================
1165 */
ReadFromSnapshot(const idBitMsgDelta & msg)1166 void idExplodingBarrel::ReadFromSnapshot( const idBitMsgDelta &msg ) {
1167 
1168 	idMoveable::ReadFromSnapshot( msg );
1169 	if ( msg.ReadBits( 1 ) ) {
1170 		Hide();
1171 	} else {
1172 		Show();
1173 	}
1174 }
1175 
1176 /*
1177 ================
1178 idExplodingBarrel::ClientReceiveEvent
1179 ================
1180 */
ClientReceiveEvent(int event,int time,const idBitMsg & msg)1181 bool idExplodingBarrel::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) {
1182 
1183 	switch( event ) {
1184 		case EVENT_EXPLODE:
1185 			if ( gameLocal.realClientTime - msg.ReadInt() < spawnArgs.GetInt( "explode_lapse", "1000" ) ) {
1186 				ExplodingEffects( );
1187 			}
1188 			return true;
1189 		default:
1190 			break;
1191 	}
1192 
1193 	return idBarrel::ClientReceiveEvent( event, time, msg );
1194 }
1195