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