]> icculus.org git repositories - icculus/iodoom3.git/blob - neo/d3xp/Grabber.cpp
hello world
[icculus/iodoom3.git] / neo / d3xp / Grabber.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 #include "../idlib/precompiled.h"
29 #pragma hdrstop
30
31 #ifdef _D3XP
32
33 #include "Game_local.h"
34 #include "Misc.h"
35
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
44
45 /*
46 ===============================================================================
47
48         Allows entities to be dragged through the world with physics.
49
50 ===============================================================================
51 */
52
53 CLASS_DECLARATION( idEntity, idGrabber )
54 END_CLASS
55
56 /*
57 ==============
58 idGrabber::idGrabber
59 ==============
60 */
61 idGrabber::idGrabber( void ) {
62         dragEnt = NULL;
63         owner = NULL;
64         beam = NULL;
65         beamTarget = NULL;
66         oldUcmdFlags = 0;
67         shakeForceFlip = false;
68         holdingAF = false;
69         endTime = 0;
70         lastFiredTime = -FIRING_DELAY;
71         dragFailTime = 0;
72         startDragTime = 0;
73         warpId = -1;
74         dragTraceDist = MAX_DRAG_TRACE_DISTANCE;
75 }
76
77 /*
78 ==============
79 idGrabber::~idGrabber
80 ==============
81 */
82 idGrabber::~idGrabber( void ) {
83         StopDrag( true );
84         if ( beam ) {
85                 delete beam;
86         }
87         if ( beamTarget ) {
88                 delete beamTarget;
89         }
90 }
91
92 /*
93 ==============
94 idGrabber::Save
95 ==============
96 */
97 void idGrabber::Save( idSaveGame *savefile ) const {
98
99         dragEnt.Save( savefile );
100         savefile->WriteStaticObject( drag );
101
102         savefile->WriteVec3( saveGravity );
103         savefile->WriteInt( id );
104
105         savefile->WriteVec3( localPlayerPoint );
106
107         owner.Save( savefile );
108
109         savefile->WriteBool( holdingAF );
110         savefile->WriteBool( shakeForceFlip );
111
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 );
119
120         savefile->WriteObject( beam );
121         savefile->WriteObject( beamTarget );
122
123         savefile->WriteInt( warpId );
124 }
125
126 /*
127 ==============
128 idGrabber::Restore
129 ==============
130 */
131 void idGrabber::Restore( idRestoreGame *savefile ) {
132         //Spawn the beams
133         Initialize();
134
135         dragEnt.Restore( savefile );
136         savefile->ReadStaticObject( drag );
137
138         savefile->ReadVec3( saveGravity );
139         savefile->ReadInt( id );
140
141         // Restore the drag force's physics object
142         if ( dragEnt.IsValid() ) {
143                 drag.SetPhysics( dragEnt.GetEntity()->GetPhysics(), id, dragEnt.GetEntity()->GetPhysics()->GetOrigin() );
144         }
145
146         savefile->ReadVec3( localPlayerPoint );
147
148         owner.Restore( savefile );
149
150         savefile->ReadBool( holdingAF );
151         savefile->ReadBool( shakeForceFlip );
152
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 );
160
161         savefile->ReadObject( reinterpret_cast<idClass *&>(beam) );
162         savefile->ReadObject( reinterpret_cast<idClass *&>(beamTarget) );
163
164         savefile->ReadInt( warpId );
165 }
166
167 /*
168 ==============
169 idGrabber::Initialize
170 ==============
171 */
172 void idGrabber::Initialize( void ) {
173         if ( !gameLocal.isMultiplayer ) {
174                 idDict args;
175
176                 if ( !beamTarget ) {
177                         args.SetVector( "origin", vec3_origin );
178                         args.SetBool( "start_off", true );
179                         beamTarget = ( idBeam * )gameLocal.SpawnEntityType( idBeam::Type, &args );
180                 }
181
182                 if ( !beam ) {
183                         args.Clear();
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 );
192                 }
193
194                 endTime = 0;
195                 dragTraceDist = MAX_DRAG_TRACE_DISTANCE;
196         }
197         else {
198                 beam = NULL;
199                 beamTarget = NULL;
200                 endTime = 0;
201                 dragTraceDist = MAX_DRAG_TRACE_DISTANCE;
202         };
203 }
204
205 /*
206 ==============
207 idGrabber::SetDragDistance
208 ==============
209 */
210 void idGrabber::SetDragDistance( float dist ) {
211         dragTraceDist = dist;
212 }
213
214 /*
215 ==============
216 idGrabber::StartDrag
217 ==============
218 */
219 void idGrabber::StartDrag( idEntity *grabEnt, int id ) {
220         int clipModelId = id;
221         idPlayer *thePlayer = owner.GetEntity();
222
223         holdingAF = false;
224         dragFailTime = gameLocal.slow.time;
225         startDragTime = gameLocal.slow.time;
226
227         oldUcmdFlags = thePlayer->usercmd.flags;
228
229         // set grabbed state for networking
230         grabEnt->SetGrabbedState( true );
231
232         // This is the new object to drag around
233         dragEnt = grabEnt;
234
235         // Show the beams!
236         UpdateBeams();
237         if ( beam ) {
238                 beam->Show();
239         }
240         if ( beamTarget ) {
241                 beamTarget->Show();
242         }
243
244         // Move the object to the fast group (helltime)
245         grabEnt->timeGroup = TIME_GROUP2;
246
247         // Handle specific class types
248         if ( grabEnt->IsType( idProjectile::Type ) ) {
249                 idProjectile* p = (idProjectile*)grabEnt;
250
251                 p->CatchProjectile( thePlayer, "_catch" );
252
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;
257                 } else {
258                         savedContents = grabEnt->GetPhysics()->GetContents();
259                         savedClipmask = grabEnt->GetPhysics()->GetClipMask();
260                 }
261                 grabEnt->GetPhysics()->SetContents( 0 );
262                 grabEnt->GetPhysics()->SetClipMask( CONTENTS_SOLID|CONTENTS_BODY );
263
264         } else if ( grabEnt->IsType( idExplodingBarrel::Type ) ) {
265                 idExplodingBarrel *ebarrel = static_cast<idExplodingBarrel*>(grabEnt);
266
267                 ebarrel->StartBurning();
268
269         } else if ( grabEnt->IsType( idAFEntity_Gibbable::Type ) ) {
270                 holdingAF = true;
271                 clipModelId = 0;
272
273                 if ( grabbableAI( grabEnt->spawnArgs.GetString( "classname" ) ) ) {
274                         idAI *aiEnt = static_cast<idAI*>(grabEnt);
275
276                         aiEnt->StartRagdoll();
277                 }
278         } else if ( grabEnt->IsType( idMoveableItem::Type ) ) {
279                 grabEnt->PostEventMS( &EV_Touch, 250, thePlayer, NULL );
280         }
281
282         // Get the current physics object to manipulate
283         idPhysics *phys = grabEnt->GetPhysics();
284
285         // Turn off gravity on object
286         saveGravity = phys->GetGravity();
287         phys->SetGravity( vec3_origin );
288
289         // hold it directly in front of player
290         localPlayerPoint = ( thePlayer->firstPersonViewAxis[0] * HOLD_DISTANCE ) * thePlayer->firstPersonViewAxis.Transpose();
291
292         // Set the ending time for the hold
293         endTime = gameLocal.time + g_grabberHoldSeconds.GetFloat() * 1000;
294
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);
298
299         // start the screen warp
300         warpId = thePlayer->playerView.AddWarp( phys->GetOrigin(), SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, 160, 2000 );
301 }
302
303 /*
304 ==============
305 idGrabber::StopDrag
306 ==============
307 */
308 void idGrabber::StopDrag( bool dropOnly ) {
309         idPlayer *thePlayer = owner.GetEntity();
310
311         if ( beam ) {
312                 beam->Hide();
313         }
314         if ( beamTarget ) {
315                 beamTarget->Hide();
316         }
317
318         if ( dragEnt.IsValid() ) {
319                 idEntity *ent = dragEnt.GetEntity();
320
321                 // set grabbed state for networking
322                 ent->SetGrabbedState( false );
323
324                 // If a cinematic has started, allow dropped object to think in cinematics
325                 if ( gameLocal.inCinematic ) {
326                         ent->cinematic = true;
327                 }
328
329                 // Restore Gravity
330                 ent->GetPhysics()->SetGravity( saveGravity );
331
332                 // Move the object back to the slow group (helltime)
333                 ent->timeGroup = TIME_GROUP1;
334
335                 if ( holdingAF ) {
336                         idAFEntity_Gibbable *af = static_cast<idAFEntity_Gibbable *>(ent);
337                         idPhysics_AF    *af_Phys = static_cast<idPhysics_AF*>(af->GetPhysics());
338
339                         if ( grabbableAI( ent->spawnArgs.GetString( "classname" ) ) ) {
340                                 idAI *aiEnt = static_cast<idAI*>(ent);
341
342                                 aiEnt->Damage( thePlayer, thePlayer, vec3_origin, "damage_suicide", 1.0f, INVALID_JOINT );
343                         }
344                         
345                         af->SetThrown( !dropOnly );
346
347                         // Reset timers so that it isn't forcibly put to rest in mid-air
348                         af_Phys->PutToRest();
349                         af_Phys->Activate();
350
351                         af_Phys->SetTimeScaleRamp( MS2SEC(gameLocal.slow.time) - 1.5f, MS2SEC(gameLocal.slow.time) + 1.0f );
352                 }
353
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 );
358
359                         if ( ent->IsType( idExplodingBarrel::Type ) ) {
360                                 idExplodingBarrel *ebarrel = static_cast<idExplodingBarrel*>(ent);
361
362                                 ebarrel->SetStability( true );
363                                 ebarrel->StopBurning();
364                         }
365                 } else {
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 );
369
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();
374
375                                 ang.pitch += 90.f;
376                                 ent->GetPhysics()->SetAxis( ang.ToMat3() );
377                                 ent->GetPhysics()->SetAngularVelocity( vec3_origin );
378
379                                 // Restore projectile contents
380                                 ent->GetPhysics()->SetContents( savedContents );
381                                 ent->GetPhysics()->SetClipMask( savedClipmask );
382
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 );
388
389                                 if ( ent->IsType( idExplodingBarrel::Type ) ) {
390                                         idExplodingBarrel *ebarrel = static_cast<idExplodingBarrel*>(ent);
391                                         ebarrel->SetStability( false );
392                                 }
393
394                         } else if ( ent->IsType( idMoveableItem::Type ) ) {
395                                 ent->GetPhysics()->SetClipMask( MASK_MONSTERSOLID );
396                         }
397                 }
398
399                 // Remove the Force_Drag's control of the entity
400                 drag.RemovePhysics( ent->GetPhysics() );
401         }
402
403         if ( warpId != -1 ) {
404                 thePlayer->playerView.FreeWarp( warpId );
405                 warpId = -1;
406         }
407
408         lastFiredTime = gameLocal.time;
409         dragEnt = NULL;
410         endTime = 0;
411 }
412
413 /*
414 ==============
415 idGrabber::Update
416 ==============
417 */
418 int idGrabber::Update( idPlayer *player, bool hide ) {
419         trace_t trace;
420         idEntity *newEnt;
421
422         // pause before allowing refire
423         if ( lastFiredTime + FIRING_DELAY > gameLocal.time ) {
424                 return 3;
425         }
426
427         // Dead players release the trigger
428         if ( hide || player->health <= 0 ) {
429                 StopDrag( true );
430                 if ( hide ) {
431                         lastFiredTime = gameLocal.time - FIRING_DELAY + 250;
432                 }
433                 return 3;
434         }
435
436         // Check if object being held has been removed (dead demon, projectile, etc.)
437         if ( endTime > gameLocal.time ) {
438                 bool abort = !dragEnt.IsValid();
439
440                 if ( !abort && dragEnt.GetEntity()->IsType( idProjectile::Type ) ) {
441                         idProjectile *proj = (idProjectile *)dragEnt.GetEntity();
442
443                         if ( proj->GetProjectileState() >= 3 ) {
444                                 abort = true;
445                         }
446                 }
447                 if ( !abort && dragEnt.GetEntity()->IsHidden() ) {
448                         abort = true;
449                 }
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) ) {
452                         abort = true;
453                 }
454         
455                 if ( abort ) {
456                         StopDrag( true );
457                         return 3;
458                 }
459         }
460
461         owner = player;
462
463         // if no entity selected for dragging
464     if ( !dragEnt.GetEntity() ) {
465                 idBounds bounds;
466                 idVec3 end = player->firstPersonViewOrigin + player->firstPersonViewAxis[0] * dragTraceDist;
467
468                 bounds.Zero();
469                 bounds.ExpandSelf( TRACE_BOUNDS_SIZE );
470
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 ];
475
476                         // if entity is already being grabbed then bypass
477                         if ( gameLocal.isMultiplayer && newEnt->IsGrabbed() ) {
478                                 return 0;
479                         }
480
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 ) {
489
490                                 bool validAF = true;
491
492                                 if ( newEnt->IsType( idAFEntity_Gibbable::Type ) ) {
493                                         idAFEntity_Gibbable *afEnt = static_cast<idAFEntity_Gibbable*>(newEnt);
494
495                                         if ( grabbableAI( newEnt->spawnArgs.GetString( "classname" ) ) ) {
496                                                 // Make sure it's also active
497                                                 if ( !afEnt->IsActive() ) {
498                                                         validAF = false;
499                                                 }
500                                         } else if ( !afEnt->IsActiveAF() ) {
501                                                 validAF = false;
502                                         }
503                                 }
504
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
510                                         return 1;
511                                 }
512                         }
513                 }
514         }
515
516         // check backwards server time in multiplayer
517         bool allow = true;
518
519         if ( gameLocal.isMultiplayer ) {
520
521                 // if we've marched backwards
522                 if ( gameLocal.slow.time < startDragTime ) {
523                         allow = false;
524                 }
525         }
526
527
528         // if there is an entity selected for dragging
529         if ( dragEnt.GetEntity() && allow ) {
530                 idPhysics *entPhys = dragEnt.GetEntity()->GetPhysics();
531                 idVec3 goalPos;
532
533                 // If the player lets go of attack, or time is up
534                 if ( !( player->usercmd.buttons & BUTTON_ATTACK ) ) {
535                         StopDrag( false );
536                         return 3;
537                 }
538                 if ( gameLocal.time > endTime ) {
539                         StopDrag( true );
540                         return 3;
541                 }
542
543                 // Check if the player is standing on the object
544                 if ( !holdingAF ) {
545                         idBounds        playerBounds;
546                         idBounds        objectBounds = entPhys->GetAbsBounds();
547                         idVec3          newPoint = player->GetPhysics()->GetOrigin();
548
549                         // create a bounds at the players feet
550                         playerBounds.Clear();
551                         playerBounds.AddPoint( newPoint );
552                         newPoint.z -= 1.f;
553                         playerBounds.AddPoint( newPoint );
554                         playerBounds.ExpandSelf( 8.f );
555
556                         // If it intersects the object bounds, then drop it
557                         if ( playerBounds.IntersectsBounds( objectBounds ) ) {
558                                 StopDrag( true );
559                                 return 3;
560                         }
561                 }
562
563                 // Shake the object at the end of the hold
564                 if ( g_grabberEnableShake.GetBool() && !gameLocal.isMultiplayer ) {
565                         ApplyShake();
566                 }
567
568                 // Set and evaluate drag force
569                 goalPos = player->firstPersonViewOrigin + localPlayerPoint * player->firstPersonViewAxis;
570
571                 drag.SetGoalPosition( goalPos );
572                 drag.Evaluate( gameLocal.time );
573
574                 // If an object is flying too fast toward the player, stop it hard
575                 if ( g_grabberHardStop.GetBool() ) {
576                         idPlane theWall;
577                         idVec3 toPlayerVelocity, objectCenter;
578                         float toPlayerSpeed;
579
580                         toPlayerVelocity = -player->firstPersonViewAxis[0];
581                         toPlayerSpeed = entPhys->GetLinearVelocity() * toPlayerVelocity;
582
583                         if ( toPlayerSpeed > 64.f ) {
584                                 objectCenter = entPhys->GetAbsBounds().GetCenter();
585
586                                 theWall.SetNormal( player->firstPersonViewAxis[0] );
587                                 theWall.FitThroughPoint( goalPos );
588
589                                 if ( theWall.Side( objectCenter, 0.1f ) == PLANESIDE_BACK ) {
590                                         int i, num;
591
592                                         num = entPhys->GetNumClipModels();
593                                         for ( i=0; i<num; i++ ) {
594                                                 entPhys->SetLinearVelocity( vec3_origin, i );
595                                         }
596                                 }
597                         }
598
599                         // Make sure the object isn't spinning too fast
600                         const float MAX_ROTATION_SPEED = 12.f;
601
602                         idVec3  angVel = entPhys->GetAngularVelocity();
603                         float   rotationSpeed = angVel.LengthFast();
604
605                         if ( rotationSpeed > MAX_ROTATION_SPEED ) {
606                                 angVel.NormalizeFast();
607                                 angVel *= MAX_ROTATION_SPEED;
608                                 entPhys->SetAngularVelocity( angVel );
609                         }
610                 }
611
612                 // Orient projectiles away from the player
613                 if ( dragEnt.GetEntity()->IsType( idProjectile::Type ) ) {
614                         idAngles ang = player->firstPersonViewAxis[0].ToAngles();
615                         ang.pitch += 90.f;
616                         entPhys->SetAxis( ang.ToMat3() );
617                 }
618
619                 // Some kind of effect from gun to object?
620                 UpdateBeams();
621
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) ) {
625                                 StopDrag( true );
626                                 return 3;
627                         }
628                 } else {
629                         dragFailTime = gameLocal.slow.time;
630                 }
631
632                 // Currently holding an object
633                 return 2;
634         }
635
636         // Not holding, nothing to hold
637         return 0;
638 }
639
640 /*
641 ======================
642 idGrabber::UpdateBeams
643 ======================
644 */
645 void idGrabber::UpdateBeams( void ) {
646         jointHandle_t   muzzle_joint;
647         idVec3  muzzle_origin;
648         idMat3  muzzle_axis;
649         renderEntity_t *re;
650
651         if ( !beam ) {
652                 return;
653         }
654
655         if ( dragEnt.IsValid() ) {
656                 idPlayer *thePlayer = owner.GetEntity();
657
658                 if ( beamTarget ) {
659                         beamTarget->SetOrigin( dragEnt.GetEntity()->GetPhysics()->GetAbsBounds().GetCenter() );
660                 }
661
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 );
665                 } else {
666                         muzzle_origin = thePlayer->GetPhysics()->GetOrigin();
667                 }
668
669                 beam->SetOrigin( muzzle_origin );
670                 re = beam->GetRenderEntity();
671                 re->origin = muzzle_origin;
672
673                 beam->UpdateVisuals();
674                 beam->Present();
675         }
676 }
677
678 /*
679 ==============
680 idGrabber::ApplyShake
681 ==============
682 */
683 void idGrabber::ApplyShake( void ) {
684         float u = 1 - (float)( endTime - gameLocal.time ) / ( g_grabberHoldSeconds.GetFloat() * 1000 );
685
686         if ( u >= 0.8f ) {
687                 idVec3 point, impulse;
688                 float shakeForceMagnitude = 450.f;
689                 float mass = dragEnt.GetEntity()->GetPhysics()->GetMass();
690
691                 shakeForceFlip = !shakeForceFlip;
692
693                 // get point to rotate around
694                 point = dragEnt.GetEntity()->GetPhysics()->GetOrigin();
695                 point.y += 1;
696
697                 // Articulated figures get less violent shake
698                 if ( holdingAF ) {
699                         shakeForceMagnitude = 120.f;
700                 }
701
702                 // calc impulse
703                 if ( shakeForceFlip ) {
704                         impulse.Set( 0, 0, shakeForceMagnitude * u * mass );
705                 }
706                 else {
707                         impulse.Set( 0, 0, -shakeForceMagnitude * u * mass );
708                 }
709
710                 dragEnt.GetEntity()->ApplyImpulse( NULL, 0, point, impulse );
711         }
712 }
713
714 /*
715 ==============
716 idGrabber::grabbableAI
717 ==============
718 */
719 bool idGrabber::grabbableAI( const char *aiName ) {
720         // skip "monster_"
721         aiName += 8;
722
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" )) {
728
729                 return true;
730         }
731
732         return false;
733 }
734
735 #endif