/* =========================================================================== Doom 3 BFG Edition GPL Source Code Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Doom 3 BFG Edition Source Code. If not, see . In addition, the Doom 3 BFG Edition 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 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. 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. =========================================================================== */ #include "../idlib/precompiled.h" #pragma hdrstop #include "Game_local.h" #include "Misc.h" #define MAX_DRAG_TRACE_DISTANCE 384.0f #define TRACE_BOUNDS_SIZE 3.f #define HOLD_DISTANCE 72.f #define FIRING_DELAY 1000.0f #define DRAG_FAIL_LEN 64.f #define THROW_SCALE 1000 #define MAX_PICKUP_VELOCITY 1500 * 1500 #define MAX_PICKUP_SIZE 96 /* =============================================================================== Allows entities to be dragged through the world with physics. =============================================================================== */ CLASS_DECLARATION( idEntity, idGrabber ) END_CLASS /* ============== idGrabber::idGrabber ============== */ idGrabber::idGrabber() { dragEnt = NULL; owner = NULL; beam = NULL; beamTarget = NULL; oldImpulseSequence = 0; shakeForceFlip = false; holdingAF = false; endTime = 0; lastFiredTime = -FIRING_DELAY; dragFailTime = 0; startDragTime = 0; warpId = -1; dragTraceDist = MAX_DRAG_TRACE_DISTANCE; } /* ============== idGrabber::~idGrabber ============== */ idGrabber::~idGrabber() { StopDrag( true ); if ( beam ) { delete beam; } if ( beamTarget ) { delete beamTarget; } } /* ============== idGrabber::Save ============== */ void idGrabber::Save( idSaveGame *savefile ) const { dragEnt.Save( savefile ); savefile->WriteStaticObject( drag ); savefile->WriteVec3( saveGravity ); savefile->WriteInt( id ); savefile->WriteVec3( localPlayerPoint ); owner.Save( savefile ); savefile->WriteBool( holdingAF ); savefile->WriteBool( shakeForceFlip ); savefile->WriteInt( endTime ); savefile->WriteInt( lastFiredTime ); savefile->WriteInt( dragFailTime ); savefile->WriteInt( startDragTime ); savefile->WriteFloat( dragTraceDist ); savefile->WriteInt( savedContents ); savefile->WriteInt( savedClipmask ); savefile->WriteObject( beam ); savefile->WriteObject( beamTarget ); savefile->WriteInt( warpId ); } /* ============== idGrabber::Restore ============== */ void idGrabber::Restore( idRestoreGame *savefile ) { //Spawn the beams Initialize(); dragEnt.Restore( savefile ); savefile->ReadStaticObject( drag ); savefile->ReadVec3( saveGravity ); savefile->ReadInt( id ); // Restore the drag force's physics object if ( dragEnt.IsValid() ) { drag.SetPhysics( dragEnt.GetEntity()->GetPhysics(), id, dragEnt.GetEntity()->GetPhysics()->GetOrigin() ); } savefile->ReadVec3( localPlayerPoint ); owner.Restore( savefile ); savefile->ReadBool( holdingAF ); savefile->ReadBool( shakeForceFlip ); savefile->ReadInt( endTime ); savefile->ReadInt( lastFiredTime ); savefile->ReadInt( dragFailTime ); savefile->ReadInt( startDragTime ); savefile->ReadFloat( dragTraceDist ); savefile->ReadInt( savedContents ); savefile->ReadInt( savedClipmask ); savefile->ReadObject( reinterpret_cast(beam) ); savefile->ReadObject( reinterpret_cast(beamTarget) ); savefile->ReadInt( warpId ); } /* ============== idGrabber::Initialize ============== */ void idGrabber::Initialize() { if ( !common->IsMultiplayer() ) { idDict args; if ( !beamTarget ) { args.SetVector( "origin", vec3_origin ); args.SetBool( "start_off", true ); beamTarget = ( idBeam * )gameLocal.SpawnEntityType( idBeam::Type, &args ); } if ( !beam ) { args.Clear(); args.Set( "target", beamTarget->name.c_str() ); args.SetVector( "origin", vec3_origin ); args.SetBool( "start_off", true ); args.Set( "width", "6" ); args.Set( "skin", "textures/smf/flareSizeable" ); args.Set( "_color", "0.0235 0.843 0.969 0.2" ); beam = ( idBeam * )gameLocal.SpawnEntityType( idBeam::Type, &args ); beam->SetShaderParm( 6, 1.0f ); } endTime = 0; dragTraceDist = MAX_DRAG_TRACE_DISTANCE; } else { beam = NULL; beamTarget = NULL; endTime = 0; dragTraceDist = MAX_DRAG_TRACE_DISTANCE; }; } /* ============== idGrabber::SetDragDistance ============== */ void idGrabber::SetDragDistance( float dist ) { dragTraceDist = dist; } /* ============== idGrabber::StartDrag ============== */ void idGrabber::StartDrag( idEntity *grabEnt, int id ) { int clipModelId = id; idPlayer *thePlayer = owner.GetEntity(); holdingAF = false; dragFailTime = gameLocal.slow.time; startDragTime = gameLocal.slow.time; oldImpulseSequence = thePlayer->usercmd.impulseSequence; // set grabbed state for networking grabEnt->SetGrabbedState( true ); // This is the new object to drag around dragEnt = grabEnt; // Show the beams! UpdateBeams(); if ( beam ) { beam->Show(); } if ( beamTarget ) { beamTarget->Show(); } // Move the object to the fast group (helltime) grabEnt->timeGroup = TIME_GROUP2; // Handle specific class types if ( grabEnt->IsType( idProjectile::Type ) ) { idProjectile* p = (idProjectile*)grabEnt; p->CatchProjectile( thePlayer, "_catch" ); // Make the projectile non-solid to other projectiles/enemies (special hack for helltime hunter) if ( !idStr::Cmp( grabEnt->GetEntityDefName(), "projectile_helltime_killer" ) ) { savedContents = CONTENTS_PROJECTILE; savedClipmask = MASK_SHOT_RENDERMODEL|CONTENTS_PROJECTILE; } else { savedContents = grabEnt->GetPhysics()->GetContents(); savedClipmask = grabEnt->GetPhysics()->GetClipMask(); } grabEnt->GetPhysics()->SetContents( 0 ); grabEnt->GetPhysics()->SetClipMask( CONTENTS_SOLID|CONTENTS_BODY ); } else if ( grabEnt->IsType( idExplodingBarrel::Type ) ) { idExplodingBarrel *ebarrel = static_cast(grabEnt); ebarrel->StartBurning(); } else if ( grabEnt->IsType( idAFEntity_Gibbable::Type ) ) { holdingAF = true; clipModelId = 0; if ( grabbableAI( grabEnt->spawnArgs.GetString( "classname" ) ) ) { idAI *aiEnt = static_cast(grabEnt); aiEnt->StartRagdoll(); } } else if ( grabEnt->IsType( idMoveableItem::Type ) ) { grabEnt->PostEventMS( &EV_Touch, 250, thePlayer, NULL ); } // Get the current physics object to manipulate idPhysics *phys = grabEnt->GetPhysics(); // Turn off gravity on object saveGravity = phys->GetGravity(); phys->SetGravity( vec3_origin ); // hold it directly in front of player localPlayerPoint = ( thePlayer->firstPersonViewAxis[0] * HOLD_DISTANCE ) * thePlayer->firstPersonViewAxis.Transpose(); // Set the ending time for the hold endTime = gameLocal.time + g_grabberHoldSeconds.GetFloat() * 1000; // Start up the Force_Drag to bring it in drag.Init( g_grabberDamping.GetFloat() ); drag.SetPhysics( phys, clipModelId, thePlayer->firstPersonViewOrigin + localPlayerPoint * thePlayer->firstPersonViewAxis); // start the screen warp warpId = thePlayer->playerView.AddWarp( phys->GetOrigin(), SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, 160, 2000 ); } /* ============== idGrabber::StopDrag ============== */ void idGrabber::StopDrag( bool dropOnly ) { idPlayer *thePlayer = owner.GetEntity(); if ( beam ) { beam->Hide(); } if ( beamTarget ) { beamTarget->Hide(); } if ( dragEnt.IsValid() ) { idEntity *ent = dragEnt.GetEntity(); // set grabbed state for networking ent->SetGrabbedState( false ); // If a cinematic has started, allow dropped object to think in cinematics if ( gameLocal.inCinematic ) { ent->cinematic = true; } // Restore Gravity ent->GetPhysics()->SetGravity( saveGravity ); // Move the object back to the slow group (helltime) ent->timeGroup = TIME_GROUP1; if ( holdingAF ) { idAFEntity_Gibbable *af = static_cast(ent); idPhysics_AF *af_Phys = static_cast(af->GetPhysics()); if ( grabbableAI( ent->spawnArgs.GetString( "classname" ) ) ) { idAI *aiEnt = static_cast(ent); aiEnt->Damage( thePlayer, thePlayer, vec3_origin, "damage_suicide", 1.0f, INVALID_JOINT ); } af->SetThrown( !dropOnly ); // Reset timers so that it isn't forcibly put to rest in mid-air af_Phys->PutToRest(); af_Phys->Activate(); af_Phys->SetTimeScaleRamp( MS2SEC(gameLocal.slow.time) - 1.5f, MS2SEC(gameLocal.slow.time) + 1.0f ); } // If the object isn't near its goal, just drop it in place. if ( !ent->IsType( idProjectile::Type ) && ( dropOnly || drag.GetDistanceToGoal() > DRAG_FAIL_LEN ) ) { ent->GetPhysics()->SetLinearVelocity( vec3_origin ); thePlayer->StartSoundShader( declManager->FindSound( "grabber_maindrop" ), SND_CHANNEL_WEAPON, 0, false, NULL ); if ( ent->IsType( idExplodingBarrel::Type ) ) { idExplodingBarrel *ebarrel = static_cast(ent); ebarrel->SetStability( true ); ebarrel->StopBurning(); } } else { // Shoot the object forward ent->ApplyImpulse( thePlayer, 0, ent->GetPhysics()->GetOrigin(), thePlayer->firstPersonViewAxis[0] * THROW_SCALE * ent->GetPhysics()->GetMass() ); thePlayer->StartSoundShader( declManager->FindSound( "grabber_release" ), SND_CHANNEL_WEAPON, 0, false, NULL ); // Orient projectiles away from the player if ( ent->IsType( idProjectile::Type ) ) { idPlayer *player = owner.GetEntity(); idAngles ang = player->firstPersonViewAxis[0].ToAngles(); ang.pitch += 90.f; ent->GetPhysics()->SetAxis( ang.ToMat3() ); ent->GetPhysics()->SetAngularVelocity( vec3_origin ); // Restore projectile contents ent->GetPhysics()->SetContents( savedContents ); ent->GetPhysics()->SetClipMask( savedClipmask ); idProjectile *projectile = static_cast< idProjectile* >( ent ); if ( projectile != NULL ) { projectile->SetLaunchedFromGrabber( true ); } } else if ( ent->IsType( idMoveable::Type ) ) { // Turn on damage for this object idMoveable *obj = static_cast(ent); obj->EnableDamage( true, 2.5f ); obj->SetAttacker( thePlayer ); if ( ent->IsType( idExplodingBarrel::Type ) ) { idExplodingBarrel *ebarrel = static_cast(ent); ebarrel->SetStability( false ); } } else if ( ent->IsType( idMoveableItem::Type ) ) { ent->GetPhysics()->SetClipMask( MASK_MONSTERSOLID ); } } // Remove the Force_Drag's control of the entity drag.RemovePhysics( ent->GetPhysics() ); } if ( warpId != -1 ) { thePlayer->playerView.FreeWarp( warpId ); warpId = -1; } lastFiredTime = gameLocal.time; dragEnt = NULL; endTime = 0; } /* ============== idGrabber::Update ============== */ int idGrabber::Update( idPlayer *player, bool hide ) { trace_t trace; idEntity *newEnt; // pause before allowing refire if ( lastFiredTime + FIRING_DELAY > gameLocal.time ) { return 3; } // Dead players release the trigger if ( hide || player->health <= 0 ) { StopDrag( true ); if ( hide ) { lastFiredTime = gameLocal.time - FIRING_DELAY + 250; } return 3; } // Check if object being held has been removed (dead demon, projectile, etc.) if ( endTime > gameLocal.time ) { bool abort = !dragEnt.IsValid(); if ( !abort && dragEnt.GetEntity()->IsType( idProjectile::Type ) ) { idProjectile *proj = (idProjectile *)dragEnt.GetEntity(); if ( proj->GetProjectileState() >= 3 ) { abort = true; } } if ( !abort && dragEnt.GetEntity() && dragEnt.GetEntity()->IsHidden() ) { abort = true; } // Not in multiplayer :: Pressing "reload" lets you carefully drop an item if ( !common->IsMultiplayer() && !abort && ( player->usercmd.impulseSequence != oldImpulseSequence ) && (player->usercmd.impulse == IMPULSE_13) ) { abort = true; } if ( abort ) { StopDrag( true ); return 3; } } owner = player; // if no entity selected for dragging if ( !dragEnt.GetEntity() ) { idBounds bounds; idVec3 end = player->firstPersonViewOrigin + player->firstPersonViewAxis[0] * dragTraceDist; bounds.Zero(); bounds.ExpandSelf( TRACE_BOUNDS_SIZE ); gameLocal.clip.TraceBounds( trace, player->firstPersonViewOrigin, end, bounds, MASK_SHOT_RENDERMODEL|CONTENTS_PROJECTILE|CONTENTS_MOVEABLECLIP, player ); // If the trace hit something if ( trace.fraction < 1.0f ) { newEnt = gameLocal.entities[ trace.c.entityNum ]; // if entity is already being grabbed then bypass if ( common->IsMultiplayer() && newEnt && newEnt->IsGrabbed() ) { return 0; } // Check if this is a valid entity to hold if ( newEnt && ( newEnt->IsType( idMoveable::Type ) || newEnt->IsType( idMoveableItem::Type ) || newEnt->IsType( idProjectile::Type ) || newEnt->IsType( idAFEntity_Gibbable::Type ) ) && newEnt->noGrab == false && newEnt->GetPhysics()->GetBounds().GetRadius() < MAX_PICKUP_SIZE && newEnt->GetPhysics()->GetLinearVelocity().LengthSqr() < MAX_PICKUP_VELOCITY ) { bool validAF = true; if ( newEnt->IsType( idAFEntity_Gibbable::Type ) ) { idAFEntity_Gibbable *afEnt = static_cast(newEnt); if ( grabbableAI( newEnt->spawnArgs.GetString( "classname" ) ) ) { // Make sure it's also active if ( !afEnt->IsActive() ) { validAF = false; } } else if ( !afEnt->IsActiveAF() ) { validAF = false; } } if ( validAF && player->usercmd.buttons & BUTTON_ATTACK ) { // Grab this entity and start dragging it around StartDrag( newEnt, trace.c.id ); } else if ( validAF ) { // A holdable object is ready to be grabbed return 1; } } } } // check backwards server time in multiplayer bool allow = true; if ( common->IsMultiplayer() ) { // if we've marched backwards if ( gameLocal.slow.time < startDragTime ) { allow = false; } } // if there is an entity selected for dragging if ( dragEnt.GetEntity() && allow ) { idPhysics *entPhys = dragEnt.GetEntity()->GetPhysics(); idVec3 goalPos; // If the player lets go of attack, or time is up if ( !( player->usercmd.buttons & BUTTON_ATTACK ) ) { StopDrag( false ); return 3; } if ( gameLocal.time > endTime ) { StopDrag( true ); return 3; } // Check if the player is standing on the object if ( !holdingAF ) { idBounds playerBounds; idBounds objectBounds = entPhys->GetAbsBounds(); idVec3 newPoint = player->GetPhysics()->GetOrigin(); // create a bounds at the players feet playerBounds.Clear(); playerBounds.AddPoint( newPoint ); newPoint.z -= 1.f; playerBounds.AddPoint( newPoint ); playerBounds.ExpandSelf( 8.f ); // If it intersects the object bounds, then drop it if ( playerBounds.IntersectsBounds( objectBounds ) ) { StopDrag( true ); return 3; } } // Shake the object at the end of the hold if ( g_grabberEnableShake.GetBool() && !common->IsMultiplayer() ) { ApplyShake(); } // Set and evaluate drag force goalPos = player->firstPersonViewOrigin + localPlayerPoint * player->firstPersonViewAxis; drag.SetGoalPosition( goalPos ); drag.Evaluate( gameLocal.time ); // If an object is flying too fast toward the player, stop it hard if ( g_grabberHardStop.GetBool() ) { idPlane theWall; idVec3 toPlayerVelocity, objectCenter; float toPlayerSpeed; toPlayerVelocity = -player->firstPersonViewAxis[0]; toPlayerSpeed = entPhys->GetLinearVelocity() * toPlayerVelocity; if ( toPlayerSpeed > 64.f ) { objectCenter = entPhys->GetAbsBounds().GetCenter(); theWall.SetNormal( player->firstPersonViewAxis[0] ); theWall.FitThroughPoint( goalPos ); if ( theWall.Side( objectCenter, 0.1f ) == PLANESIDE_BACK ) { int i, num; num = entPhys->GetNumClipModels(); for ( i=0; iSetLinearVelocity( vec3_origin, i ); } } } // Make sure the object isn't spinning too fast const float MAX_ROTATION_SPEED = 12.f; idVec3 angVel = entPhys->GetAngularVelocity(); float rotationSpeed = angVel.LengthFast(); if ( rotationSpeed > MAX_ROTATION_SPEED ) { angVel.NormalizeFast(); angVel *= MAX_ROTATION_SPEED; entPhys->SetAngularVelocity( angVel ); } } // Orient projectiles away from the player if ( dragEnt.GetEntity()->IsType( idProjectile::Type ) ) { idAngles ang = player->firstPersonViewAxis[0].ToAngles(); ang.pitch += 90.f; entPhys->SetAxis( ang.ToMat3() ); } // Some kind of effect from gun to object? UpdateBeams(); // If the object is stuck away from its intended position for more than 500ms, let it go. if ( drag.GetDistanceToGoal() > DRAG_FAIL_LEN ) { if ( dragFailTime < (gameLocal.slow.time - 500) ) { StopDrag( true ); return 3; } } else { dragFailTime = gameLocal.slow.time; } // Currently holding an object return 2; } // Not holding, nothing to hold return 0; } /* ====================== idGrabber::UpdateBeams ====================== */ void idGrabber::UpdateBeams() { jointHandle_t muzzle_joint; idVec3 muzzle_origin; idMat3 muzzle_axis; renderEntity_t *re; if ( !beam ) { return; } if ( dragEnt.IsValid() ) { idPlayer *thePlayer = owner.GetEntity(); if ( beamTarget ) { beamTarget->SetOrigin( dragEnt.GetEntity()->GetPhysics()->GetAbsBounds().GetCenter() ); } muzzle_joint = thePlayer->weapon.GetEntity()->GetAnimator()->GetJointHandle( "particle_upper" ); if ( muzzle_joint != INVALID_JOINT ) { thePlayer->weapon.GetEntity()->GetJointWorldTransform( muzzle_joint, gameLocal.time, muzzle_origin, muzzle_axis ); } else { muzzle_origin = thePlayer->GetPhysics()->GetOrigin(); } beam->SetOrigin( muzzle_origin ); re = beam->GetRenderEntity(); re->origin = muzzle_origin; beam->UpdateVisuals(); beam->Present(); } } /* ============== idGrabber::ApplyShake ============== */ void idGrabber::ApplyShake() { float u = 1 - (float)( endTime - gameLocal.time ) / ( g_grabberHoldSeconds.GetFloat() * 1000 ); if ( u >= 0.8f ) { idVec3 point, impulse; float shakeForceMagnitude = 450.f; float mass = dragEnt.GetEntity()->GetPhysics()->GetMass(); shakeForceFlip = !shakeForceFlip; // get point to rotate around point = dragEnt.GetEntity()->GetPhysics()->GetOrigin(); point.y += 1; // Articulated figures get less violent shake if ( holdingAF ) { shakeForceMagnitude = 120.f; } // calc impulse if ( shakeForceFlip ) { impulse.Set( 0, 0, shakeForceMagnitude * u * mass ); } else { impulse.Set( 0, 0, -shakeForceMagnitude * u * mass ); } dragEnt.GetEntity()->ApplyImpulse( NULL, 0, point, impulse ); } } /* ============== idGrabber::grabbableAI ============== */ bool idGrabber::grabbableAI( const char *aiName ) { // skip "monster_" aiName += 8; if (!idStr::Cmpn( aiName, "flying_lostsoul", 15 ) || !idStr::Cmpn( aiName, "demon_trite", 11 ) || !idStr::Cmp( aiName, "flying_forgotten" ) || !idStr::Cmp( aiName, "demon_cherub" ) || !idStr::Cmp( aiName, "demon_tick" )) { return true; } return false; }