2 ===========================================================================
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
7 This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).
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.
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.
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/>.
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.
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.
26 ===========================================================================
29 #include "../idlib/precompiled.h"
32 #include "Game_local.h"
35 ===============================================================================
39 ===============================================================================
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" );
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 )
56 static const float BOUNCE_SOUND_MIN_VELOCITY = 80.0f;
57 static const float BOUNCE_SOUND_MAX_VELOCITY = 200.0f;
61 idMoveable::idMoveable
64 idMoveable::idMoveable( void ) {
65 minDamageVelocity = 100.0f;
66 maxDamageVelocity = 200.0f;
67 nextCollideFxTime = 0;
71 initialSplineDir = vec3_zero;
73 unbindOnDeath = false;
83 idMoveable::~idMoveable
86 idMoveable::~idMoveable( void ) {
96 void idMoveable::Spawn( void ) {
98 float density, friction, bouncyness, mass;
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
108 if ( !collisionModelManager->TrmFromModel( clipModelName, trm ) ) {
109 gameLocal.Error( "idMoveable '%s': cannot load collision model %s", name.c_str(), clipModelName.c_str() );
113 // if the model should be shrinked
114 clipShrink = spawnArgs.GetInt( "clipshrink" );
115 if ( clipShrink != 0 ) {
116 trm.Shrink( clipShrink * CM_CLIP_EPSILON );
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" );
129 fxCollide = spawnArgs.GetString( "fx_collide" );
130 nextCollideFxTime = 0;
132 fl.takedamage = true;
133 damage = spawnArgs.GetString( "def_damage", "" );
135 monsterDamage = spawnArgs.GetString( "monster_damage", "" );
136 fl.networkSync = true;
139 canDamage = spawnArgs.GetBool( "damageWhenActive" ) ? false : true;
140 minDamageVelocity = spawnArgs.GetFloat( "minDamageVelocity", "300" ); // _D3XP
141 maxDamageVelocity = spawnArgs.GetFloat( "maxDamageVelocity", "700" ); // _D3XP
145 health = spawnArgs.GetInt( "health", "0" );
146 spawnArgs.GetString( "broken", "", brokenModel );
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() );
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 );
167 if ( spawnArgs.GetFloat( "mass", "10", mass ) ) {
168 physicsObj.SetMass( mass );
171 if ( spawnArgs.GetBool( "nodrop" ) ) {
172 physicsObj.PutToRest();
174 physicsObj.DropToFloor();
177 if ( spawnArgs.GetBool( "noimpact" ) || spawnArgs.GetBool( "notPushable" ) ) {
178 physicsObj.DisableImpact();
181 if ( spawnArgs.GetBool( "nonsolid" ) ) {
185 allowStep = spawnArgs.GetBool( "allowStep", "1" );
187 PostEventMS( &EV_SetOwnerFromSpawnArgs, 0 );
195 void idMoveable::Save( idSaveGame *savefile ) const {
197 savefile->WriteString( brokenModel );
198 savefile->WriteString( damage );
200 savefile->WriteString( monsterDamage );
201 savefile->WriteObject( attacker );
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 );
216 savefile->WriteStaticObject( physicsObj );
224 void idMoveable::Restore( idRestoreGame *savefile ) {
225 int initialSplineTime;
227 savefile->ReadString( brokenModel );
228 savefile->ReadString( damage );
230 savefile->ReadString( monsterDamage );
231 savefile->ReadObject( reinterpret_cast<idClass *&>( attacker ) );
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 );
246 if ( initialSplineTime != -1 ) {
247 InitInitialSpline( initialSplineTime );
249 initialSpline = NULL;
252 savefile->ReadStaticObject( physicsObj );
253 RestorePhysics( &physicsObj );
261 void idMoveable::Hide( void ) {
263 physicsObj.SetContents( 0 );
271 void idMoveable::Show( void ) {
273 if ( !spawnArgs.GetBool( "nonsolid" ) ) {
274 physicsObj.SetContents( CONTENTS_SOLID );
283 bool idMoveable::Collide( const trace_t &collision, const idVec3 &velocity ) {
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
296 nextSoundTime = gameLocal.time + 500;
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;
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 ) );
310 if ( ent->IsType( idAI::Type ) && hasMonsterDamage ) {
313 ent->Damage( this, attacker, dir, monsterDamage, f, INVALID_JOINT );
316 ent->Damage( this, GetPhysics()->GetClipModel()->GetOwner(), dir, monsterDamage, f, INVALID_JOINT );
319 ent->Damage( this, GetPhysics()->GetClipModel()->GetOwner(), dir, monsterDamage, f, INVALID_JOINT );
321 } else if ( hasDamage ) {
323 // in multiplayer, scale damage wrt mass of object
324 if ( gameLocal.isMultiplayer ) {
325 f *= GetPhysics()->GetMass() * g_moveableDamageScale.GetFloat();
329 ent->Damage( this, attacker, dir, damage, f, INVALID_JOINT );
332 ent->Damage( this, GetPhysics()->GetClipModel()->GetOwner(), dir, damage, f, INVALID_JOINT );
335 ent->Damage( this, GetPhysics()->GetClipModel()->GetOwner(), dir, damage, f, INVALID_JOINT );
339 nextDamageTime = gameLocal.time + 1000;
345 if ( this->IsType( idExplodingBarrel::Type ) ) {
346 idExplodingBarrel *ebarrel = static_cast<idExplodingBarrel*>(this);
348 if ( !ebarrel->IsStable() ) {
349 PostEventSec( &EV_Explode, 0.04f );
354 if ( fxCollide.Length() && gameLocal.time > nextCollideFxTime ) {
355 idEntityFx::StartFx( fxCollide, &collision.c.point, NULL, this, false );
356 nextCollideFxTime = gameLocal.time + 3500;
367 void idMoveable::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
368 if ( unbindOnDeath ) {
372 if ( brokenModel != "" ) {
373 SetModel( brokenModel );
377 if ( brokenModel == "" ) {
378 PostEventMS( &EV_Remove, 1000 );
382 if ( renderEntity.gui[ 0 ] ) {
383 renderEntity.gui[ 0 ] = NULL;
386 ActivateTargets( this );
388 fl.takedamage = false;
393 idMoveable::AllowStep
396 bool idMoveable::AllowStep( void ) const {
402 idMoveable::BecomeNonSolid
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 );
413 idMoveable::EnableDamage
416 void idMoveable::EnableDamage( bool enable, float duration ) {
418 if ( canDamage == enable ) {
425 PostEventSec( &EV_EnableDamage, duration, ( /*_D3XP*/enable ) ? 0.0f : 1.0f );
431 idMoveable::InitInitialSpline
434 void idMoveable::InitInitialSpline( int startTime ) {
435 int initialSplineTime;
437 initialSpline = GetSpline();
438 initialSplineTime = spawnArgs.GetInt( "initialSplineTime", "300" );
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 );
452 idMoveable::FollowInitialSplinePath
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 );
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 );
470 delete initialSpline;
471 initialSpline = NULL;
482 void idMoveable::Think( void ) {
483 if ( thinkFlags & TH_THINK ) {
484 if ( !FollowInitialSplinePath() ) {
485 BecomeInactive( TH_THINK );
493 idMoveable::GetRenderModelMaterial
496 const idMaterial *idMoveable::GetRenderModelMaterial( void ) const {
497 if ( renderEntity.customShader ) {
498 return renderEntity.customShader;
500 if ( renderEntity.hModel && renderEntity.hModel->NumSurfaces() ) {
501 return renderEntity.hModel->Surface( 0 )->shader;
508 idMoveable::WriteToSnapshot
511 void idMoveable::WriteToSnapshot( idBitMsgDelta &msg ) const {
512 physicsObj.WriteToSnapshot( msg );
517 idMoveable::ReadFromSnapshot
520 void idMoveable::ReadFromSnapshot( const idBitMsgDelta &msg ) {
521 physicsObj.ReadFromSnapshot( msg );
522 if ( msg.HasChanged() ) {
529 idMoveable::Event_BecomeNonSolid
532 void idMoveable::Event_BecomeNonSolid( void ) {
539 idMoveable::SetAttacker
542 void idMoveable::SetAttacker( idEntity *ent ) {
549 idMoveable::Event_Activate
552 void idMoveable::Event_Activate( idEntity *activator ) {
554 idVec3 init_velocity, init_avelocity;
558 if ( !spawnArgs.GetInt( "notPushable" ) ) {
559 physicsObj.EnableImpact();
562 physicsObj.Activate();
564 spawnArgs.GetVector( "init_velocity", "0 0 0", init_velocity );
565 spawnArgs.GetVector( "init_avelocity", "0 0 0", init_avelocity );
567 delay = spawnArgs.GetFloat( "init_velocityDelay", "0" );
568 if ( delay == 0.0f ) {
569 physicsObj.SetLinearVelocity( init_velocity );
571 PostEventSec( &EV_SetLinearVelocity, delay, init_velocity );
574 delay = spawnArgs.GetFloat( "init_avelocityDelay", "0" );
575 if ( delay == 0.0f ) {
576 physicsObj.SetAngularVelocity( init_avelocity );
578 PostEventSec( &EV_SetAngularVelocity, delay, init_avelocity );
581 InitInitialSpline( gameLocal.time );
586 idMoveable::Event_SetOwnerFromSpawnArgs
589 void idMoveable::Event_SetOwnerFromSpawnArgs( void ) {
592 if ( spawnArgs.GetString( "owner", "", owner ) ) {
593 ProcessEvent( &EV_SetOwner, gameLocal.FindEntity( owner ) );
599 idMoveable::Event_IsAtRest
602 void idMoveable::Event_IsAtRest( void ) {
603 idThread::ReturnInt( physicsObj.IsAtRest() );
608 idMoveable::Event_EnableDamage
611 void idMoveable::Event_EnableDamage( float enable ) {
613 // clear out attacker
617 canDamage = ( enable != 0.0f );
622 ===============================================================================
626 ===============================================================================
629 CLASS_DECLARATION( idMoveable, idBarrel )
637 idBarrel::idBarrel() {
642 additionalRotation = 0.0f;
643 additionalAxis.Identity();
644 fl.networkSync = true;
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 );
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 );
677 idBarrel::BarrelThink
680 void idBarrel::BarrelThink( void ) {
681 bool wasAtRest, onGround;
682 float movedDistance, rotatedDistance, angle;
683 idVec3 curOrigin, gravityNormal, dir;
684 idMat3 curAxis, axis;
686 wasAtRest = IsAtRest();
691 // only need to give the visual model an additional rotation if the physics were run
694 // current physics state
695 onGround = GetPhysics()->HasGroundContacts();
696 curOrigin = GetPhysics()->GetOrigin();
697 curAxis = GetPhysics()->GetAxis();
699 // if the barrel is on the ground
701 gravityNormal = GetPhysics()->GetGravityNormal();
703 dir = curOrigin - lastOrigin;
704 dir -= gravityNormal * dir * gravityNormal;
705 movedDistance = dir.LengthSqr();
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 ) {
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;
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;
721 // if the barrel moved further than it rotated about it's axis
722 if ( movedDistance > rotatedDistance ) {
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;
730 additionalRotation -= angle;
733 dir[barrelAxis] = 1.0f;
734 additionalAxis = idRotation( vec3_origin, dir, additionalRotation ).ToMat3();
739 // save state for next think
740 lastOrigin = curOrigin;
752 void idBarrel::Think( void ) {
753 if ( thinkFlags & TH_THINK ) {
754 if ( !FollowInitialSplinePath() ) {
755 BecomeInactive( TH_THINK );
764 idBarrel::GetPhysicsToVisualTransform
767 bool idBarrel::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) {
768 origin = vec3_origin;
769 axis = additionalAxis;
778 void idBarrel::Spawn( void ) {
779 const idBounds &bounds = GetPhysics()->GetBounds();
781 // radius of the barrel cylinder
782 radius = ( bounds[1][0] - bounds[0][0] ) * 0.5f;
784 // always a vertical barrel with cylinder axis parallel to the z-axis
787 lastOrigin = GetPhysics()->GetOrigin();
788 lastAxis = GetPhysics()->GetAxis();
790 additionalRotation = 0.0f;
791 additionalAxis.Identity();
794 fl.networkSync = true;
800 idBarrel::ClientPredictionThink
803 void idBarrel::ClientPredictionThink( void ) {
809 ===============================================================================
813 ===============================================================================
815 const idEventDef EV_Respawn( "<respawn>" );
816 const idEventDef EV_TriggerTargets( "<triggertargets>" );
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 )
827 idExplodingBarrel::idExplodingBarrel
830 idExplodingBarrel::idExplodingBarrel() {
837 particleModelDefHandle = -1;
839 memset( &particleRenderEntity, 0, sizeof( particleRenderEntity ) );
840 memset( &light, 0, sizeof( light ) );
848 idExplodingBarrel::~idExplodingBarrel
851 idExplodingBarrel::~idExplodingBarrel() {
852 if ( particleModelDefHandle >= 0 ){
853 gameRenderWorld->FreeEntityDef( particleModelDefHandle );
855 if ( lightDefHandle >= 0 ) {
856 gameRenderWorld->FreeLightDef( lightDefHandle );
862 idExplodingBarrel::Save
865 void idExplodingBarrel::Save( idSaveGame *savefile ) const {
866 savefile->WriteVec3( spawnOrigin );
867 savefile->WriteMat3( spawnAxis );
869 savefile->WriteInt( state );
870 savefile->WriteInt( particleModelDefHandle );
871 savefile->WriteInt( lightDefHandle );
873 savefile->WriteRenderEntity( particleRenderEntity );
874 savefile->WriteRenderLight( light );
876 savefile->WriteInt( particleTime );
877 savefile->WriteInt( lightTime );
878 savefile->WriteFloat( time );
881 savefile->WriteBool( isStable );
887 idExplodingBarrel::Restore
890 void idExplodingBarrel::Restore( idRestoreGame *savefile ) {
891 savefile->ReadVec3( spawnOrigin );
892 savefile->ReadMat3( spawnAxis );
894 savefile->ReadInt( (int &)state );
895 savefile->ReadInt( (int &)particleModelDefHandle );
896 savefile->ReadInt( (int &)lightDefHandle );
898 savefile->ReadRenderEntity( particleRenderEntity );
899 savefile->ReadRenderLight( light );
901 savefile->ReadInt( particleTime );
902 savefile->ReadInt( lightTime );
903 savefile->ReadFloat( time );
906 savefile->ReadBool( isStable );
908 if ( lightDefHandle != -1 ) {
909 lightDefHandle = gameRenderWorld->AddLightDef( &light );
911 if ( particleModelDefHandle != -1 ) {
912 particleModelDefHandle = gameRenderWorld->AddEntityDef( &particleRenderEntity );
919 idExplodingBarrel::Spawn
922 void idExplodingBarrel::Spawn( void ) {
923 health = spawnArgs.GetInt( "health", "5" );
924 fl.takedamage = true;
927 fl.networkSync = true;
929 spawnOrigin = GetPhysics()->GetOrigin();
930 spawnAxis = GetPhysics()->GetAxis();
932 particleModelDefHandle = -1;
936 time = spawnArgs.GetFloat( "time" );
937 memset( &particleRenderEntity, 0, sizeof( particleRenderEntity ) );
938 memset( &light, 0, sizeof( light ) );
943 idExplodingBarrel::Think
946 void idExplodingBarrel::Think( void ) {
947 idBarrel::BarrelThink();
949 if ( lightDefHandle >= 0 ){
950 if ( state == BURNING ) {
951 // ramp the color up over 250 ms
952 float pct = (gameLocal.time - lightTime) / 250.f;
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 );
964 if ( gameLocal.time - lightTime > 250 ) {
965 gameRenderWorld->FreeLightDef( lightDefHandle );
972 if ( !gameLocal.isClient && state != BURNING && state != EXPLODING ) {
973 BecomeInactive( TH_THINK );
977 if ( particleModelDefHandle >= 0 ){
978 particleRenderEntity.origin = physicsObj.GetAbsBounds().GetCenter();
979 particleRenderEntity.axis = mat3_identity;
980 gameRenderWorld->UpdateEntityDef( particleModelDefHandle, &particleRenderEntity );
987 idExplodingBarrel::SetStability
990 void idExplodingBarrel::SetStability( bool stability ) {
991 isStable = stability;
996 idExplodingBarrel::IsStable
999 bool idExplodingBarrel::IsStable( void ) {
1005 idExplodingBarrel::StartBurning
1008 void idExplodingBarrel::StartBurning( void ) {
1010 AddParticles( "barrelfire.prt", true );
1015 idExplodingBarrel::StartBurning
1018 void idExplodingBarrel::StopBurning( void ) {
1021 if ( particleModelDefHandle >= 0 ){
1022 gameRenderWorld->FreeEntityDef( particleModelDefHandle );
1023 particleModelDefHandle = -1;
1026 memset( &particleRenderEntity, 0, sizeof( particleRenderEntity ) );
1033 idExplodingBarrel::AddParticles
1036 void idExplodingBarrel::AddParticles( const char *name, bool burn ) {
1037 if ( name && *name ) {
1039 int explicitTimeGroup = timeGroup;
1040 SetTimeState explicitTS( explicitTimeGroup );
1042 if ( particleModelDefHandle >= 0 ){
1043 gameRenderWorld->FreeEntityDef( particleModelDefHandle );
1045 memset( &particleRenderEntity, 0, sizeof ( particleRenderEntity ) );
1046 const idDeclModelDef *modelDef = static_cast<const idDeclModelDef *>( declManager->FindType( DECL_MODELDEF, name ) );
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 );
1059 particleRenderEntity.timeGroup = explicitTimeGroup;
1061 if ( !particleRenderEntity.hModel ) {
1062 particleRenderEntity.hModel = renderModelManager->FindModel( name );
1064 particleModelDefHandle = gameRenderWorld->AddEntityDef( &particleRenderEntity );
1066 BecomeActive( TH_THINK );
1068 particleTime = gameLocal.realClientTime;
1075 idExplodingBarrel::AddLight
1078 void idExplodingBarrel::AddLight( const char *name, bool burn ) {
1079 if ( lightDefHandle >= 0 ){
1080 gameRenderWorld->FreeLightDef( lightDefHandle );
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 );
1101 idExplodingBarrel::ExplodingEffects
1104 void idExplodingBarrel::ExplodingEffects( void ) {
1107 StartSound( "snd_explode", SND_CHANNEL_ANY, 0, false, NULL );
1109 temp = spawnArgs.GetString( "model_damage" );
1110 if ( *temp != '\0' ) {
1115 temp = spawnArgs.GetString( "model_detonate" );
1116 if ( *temp != '\0' ) {
1117 AddParticles( temp, false );
1120 temp = spawnArgs.GetString( "mtr_lightexplode" );
1121 if ( *temp != '\0' ) {
1122 AddLight( temp, false );
1125 temp = spawnArgs.GetString( "mtr_burnmark" );
1126 if ( *temp != '\0' ) {
1127 gameLocal.ProjectDecal( GetPhysics()->GetOrigin(), GetPhysics()->GetGravity(), 128.0f, true, 96.0f, temp );
1133 idExplodingBarrel::Killed
1136 void idExplodingBarrel::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
1138 if ( IsHidden() || state == EXPLODING || state == BURNING ) {
1142 float f = spawnArgs.GetFloat( "burn" );
1143 if ( f > 0.0f && state == NORMAL ) {
1145 PostEventSec( &EV_Explode, f );
1146 StartSound( "snd_burn", SND_CHANNEL_ANY, 0, false, NULL );
1147 AddParticles( spawnArgs.GetString ( "model_burn", "" ), true );
1151 if ( gameLocal.isServer ) {
1153 byte msgBuf[MAX_EVENT_PARAM_SIZE];
1155 msg.Init( msgBuf, sizeof( msgBuf ) );
1156 msg.WriteLong( gameLocal.time );
1157 ServerSendEvent( EVENT_EXPLODE, &msg, false, -1 );
1161 // do this before applying radius damage so the ent can trace to any damagable ents nearby
1163 physicsObj.SetContents( 0 );
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 );
1170 ExplodingEffects( );
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;
1176 const idDict *debris_args = gameLocal.FindEntityDefDict( kv->GetValue(), false );
1177 if ( debris_args ) {
1182 dir = physicsObj.GetAxis()[1];
1185 dir.x += gameLocal.random.CRandomFloat() * 4.0f;
1186 dir.y += gameLocal.random.CRandomFloat() * 4.0f;
1187 //dir.z = gameLocal.random.RandomFloat() * 8.0f;
1191 gameLocal.SpawnEntityDef( *debris_args, &ent, false );
1192 if ( !ent || !ent->IsType( idDebris::Type ) ) {
1193 gameLocal.Error( "'projectile_debris' is not an idDebris" );
1196 debris = static_cast<idDebris *>(ent);
1197 debris->Create( this, physicsObj.GetOrigin(), dir.ToMat3() );
1199 debris->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = ( gameLocal.time + 1500 ) * 0.001f;
1200 debris->UpdateVisuals();
1203 kv = spawnArgs.MatchPrefix( "def_debris", kv );
1206 physicsObj.PutToRest();
1207 CancelEvents( &EV_Explode );
1208 CancelEvents( &EV_Activate );
1210 f = spawnArgs.GetFloat( "respawn" );
1212 PostEventSec( &EV_Respawn, f );
1214 PostEventMS( &EV_Remove, 5000 );
1217 if ( spawnArgs.GetBool( "triggerTargets" ) ) {
1218 ActivateTargets( this );
1224 idExplodingBarrel::Damage
1227 void idExplodingBarrel::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir,
1228 const char *damageDefName, const float damageScale, const int location ) {
1230 const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName );
1232 gameLocal.Error( "Unknown damageDef '%s'\n", damageDefName );
1234 if ( damageDef->FindKey( "radius" ) && GetPhysics()->GetContents() != 0 && GetBindMaster() == NULL ) {
1235 PostEventMS( &EV_Explode, 400 );
1237 idEntity::Damage( inflictor, attacker, dir, damageDefName, damageScale, location );
1243 idExplodingBarrel::Event_TriggerTargets
1246 void idExplodingBarrel::Event_TriggerTargets() {
1247 ActivateTargets( this );
1252 idExplodingBarrel::Event_Explode
1255 void idExplodingBarrel::Event_Explode() {
1256 if ( state == NORMAL || state == BURNING ) {
1257 state = BURNEXPIRED;
1258 Killed( NULL, NULL, 0, vec3_zero, 0 );
1264 idExplodingBarrel::Event_Respawn
1267 void idExplodingBarrel::Event_Respawn() {
1269 int minRespawnDist = spawnArgs.GetInt( "respawn_range", "256" );
1270 if ( minRespawnDist ) {
1272 for ( i = 0; i < gameLocal.numClients; i++ ) {
1273 if ( !gameLocal.entities[ i ] || !gameLocal.entities[ i ]->IsType( idPlayer::Type ) ) {
1276 idVec3 v = gameLocal.entities[ i ]->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin();
1277 float dist = v.Length();
1278 if ( minDist < 0 || dist < minDist ) {
1282 if ( minDist < minRespawnDist ) {
1283 PostEventSec( &EV_Respawn, spawnArgs.GetInt( "respawn_again", "10" ) );
1287 const char *temp = spawnArgs.GetString( "model" );
1288 if ( temp && *temp ) {
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();
1304 idMoveable::Event_Activate
1307 void idExplodingBarrel::Event_Activate( idEntity *activator ) {
1308 Killed( activator, activator, 0, vec3_origin, 0 );
1313 idMoveable::WriteToSnapshot
1316 void idExplodingBarrel::WriteToSnapshot( idBitMsgDelta &msg ) const {
1317 idMoveable::WriteToSnapshot( msg );
1318 msg.WriteBits( IsHidden(), 1 );
1323 idMoveable::ReadFromSnapshot
1326 void idExplodingBarrel::ReadFromSnapshot( const idBitMsgDelta &msg ) {
1328 idMoveable::ReadFromSnapshot( msg );
1329 if ( msg.ReadBits( 1 ) ) {
1338 idExplodingBarrel::ClientReceiveEvent
1341 bool idExplodingBarrel::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) {
1344 case EVENT_EXPLODE: {
1345 if ( gameLocal.realClientTime - msg.ReadLong() < spawnArgs.GetInt( "explode_lapse", "1000" ) ) {
1346 ExplodingEffects( );
1351 return idBarrel::ClientReceiveEvent( event, time, msg );