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 ===========================================================================
28 #include "../idlib/precompiled.h"
33 #include "Game_local.h"
36 #define MAX_DRAG_TRACE_DISTANCE 384.0f
37 #define TRACE_BOUNDS_SIZE 3.f
38 #define HOLD_DISTANCE 72.f
39 #define FIRING_DELAY 1000.0f
40 #define DRAG_FAIL_LEN 64.f
41 #define THROW_SCALE 1000
42 #define MAX_PICKUP_VELOCITY 1500 * 1500
43 #define MAX_PICKUP_SIZE 96
46 ===============================================================================
48 Allows entities to be dragged through the world with physics.
50 ===============================================================================
53 CLASS_DECLARATION( idEntity, idGrabber )
61 idGrabber::idGrabber( void ) {
67 shakeForceFlip = false;
70 lastFiredTime = -FIRING_DELAY;
74 dragTraceDist = MAX_DRAG_TRACE_DISTANCE;
82 idGrabber::~idGrabber( void ) {
97 void idGrabber::Save( idSaveGame *savefile ) const {
99 dragEnt.Save( savefile );
100 savefile->WriteStaticObject( drag );
102 savefile->WriteVec3( saveGravity );
103 savefile->WriteInt( id );
105 savefile->WriteVec3( localPlayerPoint );
107 owner.Save( savefile );
109 savefile->WriteBool( holdingAF );
110 savefile->WriteBool( shakeForceFlip );
112 savefile->WriteInt( endTime );
113 savefile->WriteInt( lastFiredTime );
114 savefile->WriteInt( dragFailTime );
115 savefile->WriteInt( startDragTime );
116 savefile->WriteFloat( dragTraceDist );
117 savefile->WriteInt( savedContents );
118 savefile->WriteInt( savedClipmask );
120 savefile->WriteObject( beam );
121 savefile->WriteObject( beamTarget );
123 savefile->WriteInt( warpId );
131 void idGrabber::Restore( idRestoreGame *savefile ) {
135 dragEnt.Restore( savefile );
136 savefile->ReadStaticObject( drag );
138 savefile->ReadVec3( saveGravity );
139 savefile->ReadInt( id );
141 // Restore the drag force's physics object
142 if ( dragEnt.IsValid() ) {
143 drag.SetPhysics( dragEnt.GetEntity()->GetPhysics(), id, dragEnt.GetEntity()->GetPhysics()->GetOrigin() );
146 savefile->ReadVec3( localPlayerPoint );
148 owner.Restore( savefile );
150 savefile->ReadBool( holdingAF );
151 savefile->ReadBool( shakeForceFlip );
153 savefile->ReadInt( endTime );
154 savefile->ReadInt( lastFiredTime );
155 savefile->ReadInt( dragFailTime );
156 savefile->ReadInt( startDragTime );
157 savefile->ReadFloat( dragTraceDist );
158 savefile->ReadInt( savedContents );
159 savefile->ReadInt( savedClipmask );
161 savefile->ReadObject( reinterpret_cast<idClass *&>(beam) );
162 savefile->ReadObject( reinterpret_cast<idClass *&>(beamTarget) );
164 savefile->ReadInt( warpId );
169 idGrabber::Initialize
172 void idGrabber::Initialize( void ) {
173 if ( !gameLocal.isMultiplayer ) {
177 args.SetVector( "origin", vec3_origin );
178 args.SetBool( "start_off", true );
179 beamTarget = ( idBeam * )gameLocal.SpawnEntityType( idBeam::Type, &args );
184 args.Set( "target", beamTarget->name.c_str() );
185 args.SetVector( "origin", vec3_origin );
186 args.SetBool( "start_off", true );
187 args.Set( "width", "6" );
188 args.Set( "skin", "textures/smf/flareSizeable" );
189 args.Set( "_color", "0.0235 0.843 0.969 0.2" );
190 beam = ( idBeam * )gameLocal.SpawnEntityType( idBeam::Type, &args );
191 beam->SetShaderParm( 6, 1.0f );
195 dragTraceDist = MAX_DRAG_TRACE_DISTANCE;
201 dragTraceDist = MAX_DRAG_TRACE_DISTANCE;
207 idGrabber::SetDragDistance
210 void idGrabber::SetDragDistance( float dist ) {
211 dragTraceDist = dist;
219 void idGrabber::StartDrag( idEntity *grabEnt, int id ) {
220 int clipModelId = id;
221 idPlayer *thePlayer = owner.GetEntity();
224 dragFailTime = gameLocal.slow.time;
225 startDragTime = gameLocal.slow.time;
227 oldUcmdFlags = thePlayer->usercmd.flags;
229 // set grabbed state for networking
230 grabEnt->SetGrabbedState( true );
232 // This is the new object to drag around
244 // Move the object to the fast group (helltime)
245 grabEnt->timeGroup = TIME_GROUP2;
247 // Handle specific class types
248 if ( grabEnt->IsType( idProjectile::Type ) ) {
249 idProjectile* p = (idProjectile*)grabEnt;
251 p->CatchProjectile( thePlayer, "_catch" );
253 // Make the projectile non-solid to other projectiles/enemies (special hack for helltime hunter)
254 if ( !idStr::Cmp( grabEnt->GetEntityDefName(), "projectile_helltime_killer" ) ) {
255 savedContents = CONTENTS_PROJECTILE;
256 savedClipmask = MASK_SHOT_RENDERMODEL|CONTENTS_PROJECTILE;
258 savedContents = grabEnt->GetPhysics()->GetContents();
259 savedClipmask = grabEnt->GetPhysics()->GetClipMask();
261 grabEnt->GetPhysics()->SetContents( 0 );
262 grabEnt->GetPhysics()->SetClipMask( CONTENTS_SOLID|CONTENTS_BODY );
264 } else if ( grabEnt->IsType( idExplodingBarrel::Type ) ) {
265 idExplodingBarrel *ebarrel = static_cast<idExplodingBarrel*>(grabEnt);
267 ebarrel->StartBurning();
269 } else if ( grabEnt->IsType( idAFEntity_Gibbable::Type ) ) {
273 if ( grabbableAI( grabEnt->spawnArgs.GetString( "classname" ) ) ) {
274 idAI *aiEnt = static_cast<idAI*>(grabEnt);
276 aiEnt->StartRagdoll();
278 } else if ( grabEnt->IsType( idMoveableItem::Type ) ) {
279 grabEnt->PostEventMS( &EV_Touch, 250, thePlayer, NULL );
282 // Get the current physics object to manipulate
283 idPhysics *phys = grabEnt->GetPhysics();
285 // Turn off gravity on object
286 saveGravity = phys->GetGravity();
287 phys->SetGravity( vec3_origin );
289 // hold it directly in front of player
290 localPlayerPoint = ( thePlayer->firstPersonViewAxis[0] * HOLD_DISTANCE ) * thePlayer->firstPersonViewAxis.Transpose();
292 // Set the ending time for the hold
293 endTime = gameLocal.time + g_grabberHoldSeconds.GetFloat() * 1000;
295 // Start up the Force_Drag to bring it in
296 drag.Init( g_grabberDamping.GetFloat() );
297 drag.SetPhysics( phys, clipModelId, thePlayer->firstPersonViewOrigin + localPlayerPoint * thePlayer->firstPersonViewAxis);
299 // start the screen warp
300 warpId = thePlayer->playerView.AddWarp( phys->GetOrigin(), SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, 160, 2000 );
308 void idGrabber::StopDrag( bool dropOnly ) {
309 idPlayer *thePlayer = owner.GetEntity();
318 if ( dragEnt.IsValid() ) {
319 idEntity *ent = dragEnt.GetEntity();
321 // set grabbed state for networking
322 ent->SetGrabbedState( false );
324 // If a cinematic has started, allow dropped object to think in cinematics
325 if ( gameLocal.inCinematic ) {
326 ent->cinematic = true;
330 ent->GetPhysics()->SetGravity( saveGravity );
332 // Move the object back to the slow group (helltime)
333 ent->timeGroup = TIME_GROUP1;
336 idAFEntity_Gibbable *af = static_cast<idAFEntity_Gibbable *>(ent);
337 idPhysics_AF *af_Phys = static_cast<idPhysics_AF*>(af->GetPhysics());
339 if ( grabbableAI( ent->spawnArgs.GetString( "classname" ) ) ) {
340 idAI *aiEnt = static_cast<idAI*>(ent);
342 aiEnt->Damage( thePlayer, thePlayer, vec3_origin, "damage_suicide", 1.0f, INVALID_JOINT );
345 af->SetThrown( !dropOnly );
347 // Reset timers so that it isn't forcibly put to rest in mid-air
348 af_Phys->PutToRest();
351 af_Phys->SetTimeScaleRamp( MS2SEC(gameLocal.slow.time) - 1.5f, MS2SEC(gameLocal.slow.time) + 1.0f );
354 // If the object isn't near its goal, just drop it in place.
355 if ( !ent->IsType( idProjectile::Type ) && ( dropOnly || drag.GetDistanceToGoal() > DRAG_FAIL_LEN ) ) {
356 ent->GetPhysics()->SetLinearVelocity( vec3_origin );
357 thePlayer->StartSoundShader( declManager->FindSound( "grabber_maindrop" ), SND_CHANNEL_WEAPON, 0, false, NULL );
359 if ( ent->IsType( idExplodingBarrel::Type ) ) {
360 idExplodingBarrel *ebarrel = static_cast<idExplodingBarrel*>(ent);
362 ebarrel->SetStability( true );
363 ebarrel->StopBurning();
366 // Shoot the object forward
367 ent->ApplyImpulse( thePlayer, 0, ent->GetPhysics()->GetOrigin(), thePlayer->firstPersonViewAxis[0] * THROW_SCALE * ent->GetPhysics()->GetMass() );
368 thePlayer->StartSoundShader( declManager->FindSound( "grabber_release" ), SND_CHANNEL_WEAPON, 0, false, NULL );
370 // Orient projectiles away from the player
371 if ( ent->IsType( idProjectile::Type ) ) {
372 idPlayer *player = owner.GetEntity();
373 idAngles ang = player->firstPersonViewAxis[0].ToAngles();
376 ent->GetPhysics()->SetAxis( ang.ToMat3() );
377 ent->GetPhysics()->SetAngularVelocity( vec3_origin );
379 // Restore projectile contents
380 ent->GetPhysics()->SetContents( savedContents );
381 ent->GetPhysics()->SetClipMask( savedClipmask );
383 } else if ( ent->IsType( idMoveable::Type ) ) {
384 // Turn on damage for this object
385 idMoveable *obj = static_cast<idMoveable*>(ent);
386 obj->EnableDamage( true, 2.5f );
387 obj->SetAttacker( thePlayer );
389 if ( ent->IsType( idExplodingBarrel::Type ) ) {
390 idExplodingBarrel *ebarrel = static_cast<idExplodingBarrel*>(ent);
391 ebarrel->SetStability( false );
394 } else if ( ent->IsType( idMoveableItem::Type ) ) {
395 ent->GetPhysics()->SetClipMask( MASK_MONSTERSOLID );
399 // Remove the Force_Drag's control of the entity
400 drag.RemovePhysics( ent->GetPhysics() );
403 if ( warpId != -1 ) {
404 thePlayer->playerView.FreeWarp( warpId );
408 lastFiredTime = gameLocal.time;
418 int idGrabber::Update( idPlayer *player, bool hide ) {
422 // pause before allowing refire
423 if ( lastFiredTime + FIRING_DELAY > gameLocal.time ) {
427 // Dead players release the trigger
428 if ( hide || player->health <= 0 ) {
431 lastFiredTime = gameLocal.time - FIRING_DELAY + 250;
436 // Check if object being held has been removed (dead demon, projectile, etc.)
437 if ( endTime > gameLocal.time ) {
438 bool abort = !dragEnt.IsValid();
440 if ( !abort && dragEnt.GetEntity()->IsType( idProjectile::Type ) ) {
441 idProjectile *proj = (idProjectile *)dragEnt.GetEntity();
443 if ( proj->GetProjectileState() >= 3 ) {
447 if ( !abort && dragEnt.GetEntity()->IsHidden() ) {
450 // Not in multiplayer :: Pressing "reload" lets you carefully drop an item
451 if ( !gameLocal.isMultiplayer && !abort && (( player->usercmd.flags & UCF_IMPULSE_SEQUENCE ) != ( oldUcmdFlags & UCF_IMPULSE_SEQUENCE )) && (player->usercmd.impulse == IMPULSE_13) ) {
463 // if no entity selected for dragging
464 if ( !dragEnt.GetEntity() ) {
466 idVec3 end = player->firstPersonViewOrigin + player->firstPersonViewAxis[0] * dragTraceDist;
469 bounds.ExpandSelf( TRACE_BOUNDS_SIZE );
471 gameLocal.clip.TraceBounds( trace, player->firstPersonViewOrigin, end, bounds, MASK_SHOT_RENDERMODEL|CONTENTS_PROJECTILE|CONTENTS_MOVEABLECLIP, player );
472 // If the trace hit something
473 if ( trace.fraction < 1.0f ) {
474 newEnt = gameLocal.entities[ trace.c.entityNum ];
476 // if entity is already being grabbed then bypass
477 if ( gameLocal.isMultiplayer && newEnt->IsGrabbed() ) {
481 // Check if this is a valid entity to hold
482 if ( newEnt && ( newEnt->IsType( idMoveable::Type ) ||
483 newEnt->IsType( idMoveableItem::Type ) ||
484 newEnt->IsType( idProjectile::Type ) ||
485 newEnt->IsType( idAFEntity_Gibbable::Type ) ) &&
486 newEnt->noGrab == false &&
487 newEnt->GetPhysics()->GetBounds().GetRadius() < MAX_PICKUP_SIZE &&
488 newEnt->GetPhysics()->GetLinearVelocity().LengthSqr() < MAX_PICKUP_VELOCITY ) {
492 if ( newEnt->IsType( idAFEntity_Gibbable::Type ) ) {
493 idAFEntity_Gibbable *afEnt = static_cast<idAFEntity_Gibbable*>(newEnt);
495 if ( grabbableAI( newEnt->spawnArgs.GetString( "classname" ) ) ) {
496 // Make sure it's also active
497 if ( !afEnt->IsActive() ) {
500 } else if ( !afEnt->IsActiveAF() ) {
505 if ( validAF && player->usercmd.buttons & BUTTON_ATTACK ) {
506 // Grab this entity and start dragging it around
507 StartDrag( newEnt, trace.c.id );
508 } else if ( validAF ) {
509 // A holdable object is ready to be grabbed
516 // check backwards server time in multiplayer
519 if ( gameLocal.isMultiplayer ) {
521 // if we've marched backwards
522 if ( gameLocal.slow.time < startDragTime ) {
528 // if there is an entity selected for dragging
529 if ( dragEnt.GetEntity() && allow ) {
530 idPhysics *entPhys = dragEnt.GetEntity()->GetPhysics();
533 // If the player lets go of attack, or time is up
534 if ( !( player->usercmd.buttons & BUTTON_ATTACK ) ) {
538 if ( gameLocal.time > endTime ) {
543 // Check if the player is standing on the object
545 idBounds playerBounds;
546 idBounds objectBounds = entPhys->GetAbsBounds();
547 idVec3 newPoint = player->GetPhysics()->GetOrigin();
549 // create a bounds at the players feet
550 playerBounds.Clear();
551 playerBounds.AddPoint( newPoint );
553 playerBounds.AddPoint( newPoint );
554 playerBounds.ExpandSelf( 8.f );
556 // If it intersects the object bounds, then drop it
557 if ( playerBounds.IntersectsBounds( objectBounds ) ) {
563 // Shake the object at the end of the hold
564 if ( g_grabberEnableShake.GetBool() && !gameLocal.isMultiplayer ) {
568 // Set and evaluate drag force
569 goalPos = player->firstPersonViewOrigin + localPlayerPoint * player->firstPersonViewAxis;
571 drag.SetGoalPosition( goalPos );
572 drag.Evaluate( gameLocal.time );
574 // If an object is flying too fast toward the player, stop it hard
575 if ( g_grabberHardStop.GetBool() ) {
577 idVec3 toPlayerVelocity, objectCenter;
580 toPlayerVelocity = -player->firstPersonViewAxis[0];
581 toPlayerSpeed = entPhys->GetLinearVelocity() * toPlayerVelocity;
583 if ( toPlayerSpeed > 64.f ) {
584 objectCenter = entPhys->GetAbsBounds().GetCenter();
586 theWall.SetNormal( player->firstPersonViewAxis[0] );
587 theWall.FitThroughPoint( goalPos );
589 if ( theWall.Side( objectCenter, 0.1f ) == PLANESIDE_BACK ) {
592 num = entPhys->GetNumClipModels();
593 for ( i=0; i<num; i++ ) {
594 entPhys->SetLinearVelocity( vec3_origin, i );
599 // Make sure the object isn't spinning too fast
600 const float MAX_ROTATION_SPEED = 12.f;
602 idVec3 angVel = entPhys->GetAngularVelocity();
603 float rotationSpeed = angVel.LengthFast();
605 if ( rotationSpeed > MAX_ROTATION_SPEED ) {
606 angVel.NormalizeFast();
607 angVel *= MAX_ROTATION_SPEED;
608 entPhys->SetAngularVelocity( angVel );
612 // Orient projectiles away from the player
613 if ( dragEnt.GetEntity()->IsType( idProjectile::Type ) ) {
614 idAngles ang = player->firstPersonViewAxis[0].ToAngles();
616 entPhys->SetAxis( ang.ToMat3() );
619 // Some kind of effect from gun to object?
622 // If the object is stuck away from its intended position for more than 500ms, let it go.
623 if ( drag.GetDistanceToGoal() > DRAG_FAIL_LEN ) {
624 if ( dragFailTime < (gameLocal.slow.time - 500) ) {
629 dragFailTime = gameLocal.slow.time;
632 // Currently holding an object
636 // Not holding, nothing to hold
641 ======================
642 idGrabber::UpdateBeams
643 ======================
645 void idGrabber::UpdateBeams( void ) {
646 jointHandle_t muzzle_joint;
647 idVec3 muzzle_origin;
655 if ( dragEnt.IsValid() ) {
656 idPlayer *thePlayer = owner.GetEntity();
659 beamTarget->SetOrigin( dragEnt.GetEntity()->GetPhysics()->GetAbsBounds().GetCenter() );
662 muzzle_joint = thePlayer->weapon.GetEntity()->GetAnimator()->GetJointHandle( "particle_upper" );
663 if ( muzzle_joint != INVALID_JOINT ) {
664 thePlayer->weapon.GetEntity()->GetJointWorldTransform( muzzle_joint, gameLocal.time, muzzle_origin, muzzle_axis );
666 muzzle_origin = thePlayer->GetPhysics()->GetOrigin();
669 beam->SetOrigin( muzzle_origin );
670 re = beam->GetRenderEntity();
671 re->origin = muzzle_origin;
673 beam->UpdateVisuals();
680 idGrabber::ApplyShake
683 void idGrabber::ApplyShake( void ) {
684 float u = 1 - (float)( endTime - gameLocal.time ) / ( g_grabberHoldSeconds.GetFloat() * 1000 );
687 idVec3 point, impulse;
688 float shakeForceMagnitude = 450.f;
689 float mass = dragEnt.GetEntity()->GetPhysics()->GetMass();
691 shakeForceFlip = !shakeForceFlip;
693 // get point to rotate around
694 point = dragEnt.GetEntity()->GetPhysics()->GetOrigin();
697 // Articulated figures get less violent shake
699 shakeForceMagnitude = 120.f;
703 if ( shakeForceFlip ) {
704 impulse.Set( 0, 0, shakeForceMagnitude * u * mass );
707 impulse.Set( 0, 0, -shakeForceMagnitude * u * mass );
710 dragEnt.GetEntity()->ApplyImpulse( NULL, 0, point, impulse );
716 idGrabber::grabbableAI
719 bool idGrabber::grabbableAI( const char *aiName ) {
723 if (!idStr::Cmpn( aiName, "flying_lostsoul", 15 ) ||
724 !idStr::Cmpn( aiName, "demon_trite", 11 ) ||
725 !idStr::Cmp( aiName, "flying_forgotten" ) ||
726 !idStr::Cmp( aiName, "demon_cherub" ) ||
727 !idStr::Cmp( aiName, "demon_tick" )) {