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